1 /* vi: set sw=8 ts=8: */
3 * tiny vi.c: A small 'vi' clone
4 * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 static const char vi_Version[] =
22 "$Id: vi.c,v 1.24 2002/10/26 10:19:19 andersen Exp $";
25 * To compile for standalone use:
26 * gcc -Wall -Os -s -DSTANDALONE -o vi vi.c
28 * gcc -Wall -Os -s -DSTANDALONE -DCONFIG_FEATURE_VI_CRASHME -o vi vi.c # include testing features
35 * $HOME/.exrc and ./.exrc
36 * add magic to search /foo.*bar
39 * how about mode lines: vi: set sw=8 ts=8:
40 * if mark[] values were line numbers rather than pointers
41 * it would be easier to change the mark when add/delete lines
42 * More intelligence in refresh()
43 * ":r !cmd" and "!cmd" to filter text through an external command
44 * A true "undo" facility
45 * An "ex" line oriented mode- maybe using "cmdedit"
48 //---- Feature -------------- Bytes to immplement
51 #define CONFIG_FEATURE_VI_COLON // 4288
52 #define CONFIG_FEATURE_VI_YANKMARK // 1408
53 #define CONFIG_FEATURE_VI_SEARCH // 1088
54 #define CONFIG_FEATURE_VI_USE_SIGNALS // 1056
55 #define CONFIG_FEATURE_VI_DOT_CMD // 576
56 #define CONFIG_FEATURE_VI_READONLY // 128
57 #define CONFIG_FEATURE_VI_SETOPTS // 576
58 #define CONFIG_FEATURE_VI_SET // 224
59 #define CONFIG_FEATURE_VI_WIN_RESIZE // 256 WIN_RESIZE
60 // To test editor using CRASHME:
62 // To stop testing, wait until all to text[] is deleted, or
63 // Ctrl-Z and kill -9 %1
64 // while in the editor Ctrl-T will toggle the crashme function on and off.
65 //#define CONFIG_FEATURE_VI_CRASHME // randomly pick commands to execute
66 #endif /* STANDALONE */
73 #include <sys/ioctl.h>
75 #include <sys/types.h>
88 #endif /* STANDALONE */
92 #define FALSE ((int)0)
94 #define MAX_SCR_COLS BUFSIZ
96 // Misc. non-Ascii keys that report an escape sequence
97 #define VI_K_UP 128 // cursor key Up
98 #define VI_K_DOWN 129 // cursor key Down
99 #define VI_K_RIGHT 130 // Cursor Key Right
100 #define VI_K_LEFT 131 // cursor key Left
101 #define VI_K_HOME 132 // Cursor Key Home
102 #define VI_K_END 133 // Cursor Key End
103 #define VI_K_INSERT 134 // Cursor Key Insert
104 #define VI_K_PAGEUP 135 // Cursor Key Page Up
105 #define VI_K_PAGEDOWN 136 // Cursor Key Page Down
106 #define VI_K_FUN1 137 // Function Key F1
107 #define VI_K_FUN2 138 // Function Key F2
108 #define VI_K_FUN3 139 // Function Key F3
109 #define VI_K_FUN4 140 // Function Key F4
110 #define VI_K_FUN5 141 // Function Key F5
111 #define VI_K_FUN6 142 // Function Key F6
112 #define VI_K_FUN7 143 // Function Key F7
113 #define VI_K_FUN8 144 // Function Key F8
114 #define VI_K_FUN9 145 // Function Key F9
115 #define VI_K_FUN10 146 // Function Key F10
116 #define VI_K_FUN11 147 // Function Key F11
117 #define VI_K_FUN12 148 // Function Key F12
119 static const int YANKONLY = FALSE;
120 static const int YANKDEL = TRUE;
121 static const int FORWARD = 1; // code depends on "1" for array index
122 static const int BACK = -1; // code depends on "-1" for array index
123 static const int LIMITED = 0; // how much of text[] in char_search
124 static const int FULL = 1; // how much of text[] in char_search
126 static const int S_BEFORE_WS = 1; // used in skip_thing() for moving "dot"
127 static const int S_TO_WS = 2; // used in skip_thing() for moving "dot"
128 static const int S_OVER_WS = 3; // used in skip_thing() for moving "dot"
129 static const int S_END_PUNCT = 4; // used in skip_thing() for moving "dot"
130 static const int S_END_ALNUM = 5; // used in skip_thing() for moving "dot"
132 typedef unsigned char Byte;
135 static int editing; // >0 while we are editing a file
136 static int cmd_mode; // 0=command 1=insert
137 static int file_modified; // buffer contents changed
138 static int err_method; // indicate error with beep or flash
139 static int fn_start; // index of first cmd line file name
140 static int save_argc; // how many file names on cmd line
141 static int cmdcnt; // repetition count
142 static fd_set rfds; // use select() for small sleeps
143 static struct timeval tv; // use select() for small sleeps
144 static char erase_char; // the users erase character
145 static int rows, columns; // the terminal screen is this size
146 static int crow, ccol, offset; // cursor is on Crow x Ccol with Horz Ofset
147 static char *SOs, *SOn; // terminal standout start/normal ESC sequence
148 static char *bell; // terminal bell sequence
149 static char *Ceol, *Ceos; // Clear-end-of-line and Clear-end-of-screen ESC sequence
150 static char *CMrc; // Cursor motion arbitrary destination ESC sequence
151 static char *CMup, *CMdown; // Cursor motion up and down ESC sequence
152 static Byte *status_buffer; // mesages to the user
153 static Byte last_input_char; // last char read from user
154 static Byte last_forward_char; // last char searched for with 'f'
155 static Byte *cfn; // previous, current, and next file name
156 static Byte *text, *end, *textend; // pointers to the user data in memory
157 static Byte *screen; // pointer to the virtual screen buffer
158 static int screensize; // and its size
159 static Byte *screenbegin; // index into text[], of top line on the screen
160 static Byte *dot; // where all the action takes place
162 static struct termios term_orig, term_vi; // remember what the cooked mode was
164 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
165 static int last_row; // where the cursor was last moved to
166 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
167 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
168 static jmp_buf restart; // catch_sig()
169 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
170 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
171 static struct winsize winsize; // remember the window size
172 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
173 #ifdef CONFIG_FEATURE_VI_DOT_CMD
174 static int adding2q; // are we currently adding user input to q
175 static Byte *last_modifying_cmd; // last modifying cmd for "."
176 static Byte *ioq, *ioq_start; // pointer to string for get_one_char to "read"
177 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
178 #if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK)
179 static Byte *modifying_cmds; // cmds that modify text[]
180 #endif /* CONFIG_FEATURE_VI_DOT_CMD || CONFIG_FEATURE_VI_YANKMARK */
181 #ifdef CONFIG_FEATURE_VI_READONLY
182 static int vi_readonly, readonly;
183 #endif /* CONFIG_FEATURE_VI_READONLY */
184 #ifdef CONFIG_FEATURE_VI_SETOPTS
185 static int autoindent;
186 static int showmatch;
187 static int ignorecase;
188 #endif /* CONFIG_FEATURE_VI_SETOPTS */
189 #ifdef CONFIG_FEATURE_VI_YANKMARK
190 static Byte *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
191 static int YDreg, Ureg; // default delete register and orig line for "U"
192 static Byte *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
193 static Byte *context_start, *context_end;
194 #endif /* CONFIG_FEATURE_VI_YANKMARK */
195 #ifdef CONFIG_FEATURE_VI_SEARCH
196 static Byte *last_search_pattern; // last pattern from a '/' or '?' search
197 #endif /* CONFIG_FEATURE_VI_SEARCH */
200 static void edit_file(Byte *); // edit one file
201 static void do_cmd(Byte); // execute a command
202 static void sync_cursor(Byte *, int *, int *); // synchronize the screen cursor to dot
203 static Byte *begin_line(Byte *); // return pointer to cur line B-o-l
204 static Byte *end_line(Byte *); // return pointer to cur line E-o-l
205 extern inline Byte *dollar_line(Byte *); // return pointer to just before NL
206 static Byte *prev_line(Byte *); // return pointer to prev line B-o-l
207 static Byte *next_line(Byte *); // return pointer to next line B-o-l
208 static Byte *end_screen(void); // get pointer to last char on screen
209 static int count_lines(Byte *, Byte *); // count line from start to stop
210 static Byte *find_line(int); // find begining of line #li
211 static Byte *move_to_col(Byte *, int); // move "p" to column l
212 static int isblnk(Byte); // is the char a blank or tab
213 static void dot_left(void); // move dot left- dont leave line
214 static void dot_right(void); // move dot right- dont leave line
215 static void dot_begin(void); // move dot to B-o-l
216 static void dot_end(void); // move dot to E-o-l
217 static void dot_next(void); // move dot to next line B-o-l
218 static void dot_prev(void); // move dot to prev line B-o-l
219 static void dot_scroll(int, int); // move the screen up or down
220 static void dot_skip_over_ws(void); // move dot pat WS
221 static void dot_delete(void); // delete the char at 'dot'
222 static Byte *bound_dot(Byte *); // make sure text[0] <= P < "end"
223 static Byte *new_screen(int, int); // malloc virtual screen memory
224 static Byte *new_text(int); // malloc memory for text[] buffer
225 static Byte *char_insert(Byte *, Byte); // insert the char c at 'p'
226 static Byte *stupid_insert(Byte *, Byte); // stupidly insert the char c at 'p'
227 static Byte find_range(Byte **, Byte **, Byte); // return pointers for an object
228 static int st_test(Byte *, int, int, Byte *); // helper for skip_thing()
229 static Byte *skip_thing(Byte *, int, int, int); // skip some object
230 static Byte *find_pair(Byte *, Byte); // find matching pair () [] {}
231 static Byte *text_hole_delete(Byte *, Byte *); // at "p", delete a 'size' byte hole
232 static Byte *text_hole_make(Byte *, int); // at "p", make a 'size' byte hole
233 static Byte *yank_delete(Byte *, Byte *, int, int); // yank text[] into register then delete
234 static void show_help(void); // display some help info
235 extern inline void print_literal(Byte *, Byte *); // copy s to buf, convert unprintable
236 static void rawmode(void); // set "raw" mode on tty
237 static void cookmode(void); // return to "cooked" mode on tty
238 static int mysleep(int); // sleep for 'h' 1/100 seconds
239 static Byte readit(void); // read (maybe cursor) key from stdin
240 static Byte get_one_char(void); // read 1 char from stdin
241 static int file_size(Byte *); // what is the byte size of "fn"
242 static int file_insert(Byte *, Byte *, int);
243 static int file_write(Byte *, Byte *, Byte *);
244 static void place_cursor(int, int, int);
245 static void screen_erase(void);
246 static void clear_to_eol(void);
247 static void clear_to_eos(void);
248 static void standout_start(void); // send "start reverse video" sequence
249 static void standout_end(void); // send "end reverse video" sequence
250 static void flash(int); // flash the terminal screen
251 static void beep(void); // beep the terminal
252 static void indicate_error(char); // use flash or beep to indicate error
253 static void show_status_line(void); // put a message on the bottom line
254 static void psb(char *, ...); // Print Status Buf
255 static void psbs(char *, ...); // Print Status Buf in standout mode
256 static void ni(Byte *); // display messages
257 static void edit_status(void); // show file status on status line
258 static void redraw(int); // force a full screen refresh
259 static void format_line(Byte*, Byte*, int);
260 static void refresh(int); // update the terminal from screen[]
262 #ifdef CONFIG_FEATURE_VI_SEARCH
263 static Byte *char_search(Byte *, Byte *, int, int); // search for pattern starting at p
264 static int mycmp(Byte *, Byte *, int); // string cmp based in "ignorecase"
265 #endif /* CONFIG_FEATURE_VI_SEARCH */
266 #ifdef CONFIG_FEATURE_VI_COLON
267 static void Hit_Return(void);
268 static Byte *get_one_address(Byte *, int *); // get colon addr, if present
269 static Byte *get_address(Byte *, int *, int *); // get two colon addrs, if present
270 static void colon(Byte *); // execute the "colon" mode cmds
271 #endif /* CONFIG_FEATURE_VI_COLON */
272 static Byte *get_input_line(Byte *); // get input line- use "status line"
273 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
274 static void winch_sig(int); // catch window size changes
275 static void suspend_sig(int); // catch ctrl-Z
276 static void alarm_sig(int); // catch alarm time-outs
277 static void catch_sig(int); // catch ctrl-C
278 static void core_sig(int); // catch a core dump signal
279 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
280 #ifdef CONFIG_FEATURE_VI_DOT_CMD
281 static void start_new_cmd_q(Byte); // new queue for command
282 static void end_cmd_q(void); // stop saving input chars
283 #else /* CONFIG_FEATURE_VI_DOT_CMD */
285 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
286 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
287 static void window_size_get(int); // find out what size the window is
288 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
289 #ifdef CONFIG_FEATURE_VI_SETOPTS
290 static void showmatching(Byte *); // show the matching pair () [] {}
291 #endif /* CONFIG_FEATURE_VI_SETOPTS */
292 #if defined(CONFIG_FEATURE_VI_YANKMARK) || defined(CONFIG_FEATURE_VI_COLON) || defined(CONFIG_FEATURE_VI_CRASHME)
293 static Byte *string_insert(Byte *, Byte *); // insert the string at 'p'
294 #endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */
295 #ifdef CONFIG_FEATURE_VI_YANKMARK
296 static Byte *text_yank(Byte *, Byte *, int); // save copy of "p" into a register
297 static Byte what_reg(void); // what is letter of current YDreg
298 static void check_context(Byte); // remember context for '' command
299 extern inline Byte *swap_context(Byte *); // goto new context for '' command
300 #endif /* CONFIG_FEATURE_VI_YANKMARK */
301 #ifdef CONFIG_FEATURE_VI_CRASHME
302 static void crash_dummy();
303 static void crash_test();
304 static int crashme = 0;
305 #endif /* CONFIG_FEATURE_VI_CRASHME */
308 extern int vi_main(int argc, char **argv)
312 #ifdef CONFIG_FEATURE_VI_YANKMARK
314 #endif /* CONFIG_FEATURE_VI_YANKMARK */
316 CMrc= "\033[%d;%dH"; // Terminal Crusor motion ESC sequence
317 CMup= "\033[A"; // move cursor up one line, same col
318 CMdown="\n"; // move cursor down one line, same col
319 Ceol= "\033[0K"; // Clear from cursor to end of line
320 Ceos= "\033[0J"; // Clear from cursor to end of screen
321 SOs = "\033[7m"; // Terminal standout mode on
322 SOn = "\033[0m"; // Terminal standout mode off
323 bell= "\007"; // Terminal bell sequence
324 #ifdef CONFIG_FEATURE_VI_CRASHME
325 (void) srand((long) getpid());
326 #endif /* CONFIG_FEATURE_VI_CRASHME */
327 status_buffer = (Byte *) xmalloc(200); // hold messages to user
328 #ifdef CONFIG_FEATURE_VI_READONLY
329 vi_readonly = readonly = FALSE;
330 if (strncmp(argv[0], "view", 4) == 0) {
334 #endif /* CONFIG_FEATURE_VI_READONLY */
335 #ifdef CONFIG_FEATURE_VI_SETOPTS
339 #endif /* CONFIG_FEATURE_VI_SETOPTS */
340 #ifdef CONFIG_FEATURE_VI_YANKMARK
341 for (i = 0; i < 28; i++) {
343 } // init the yank regs
344 #endif /* CONFIG_FEATURE_VI_YANKMARK */
345 #if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK)
346 modifying_cmds = (Byte *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[]
347 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
349 // 1- process $HOME/.exrc file
350 // 2- process EXINIT variable from environment
351 // 3- process command line args
352 while ((c = getopt(argc, argv, "hCR")) != -1) {
354 #ifdef CONFIG_FEATURE_VI_CRASHME
358 #endif /* CONFIG_FEATURE_VI_CRASHME */
359 #ifdef CONFIG_FEATURE_VI_READONLY
360 case 'R': // Read-only flag
363 #endif /* CONFIG_FEATURE_VI_READONLY */
364 //case 'r': // recover flag- ignore- we don't use tmp file
365 //case 'x': // encryption flag- ignore
366 //case 'c': // execute command first
367 //case 'h': // help -- just use default
374 // The argv array can be used by the ":next" and ":rewind" commands
376 fn_start = optind; // remember first file name for :next and :rew
379 //----- This is the main file handling loop --------------
380 if (optind >= argc) {
381 editing = 1; // 0= exit, 1= one file, 2= multiple files
384 for (; optind < argc; optind++) {
385 editing = 1; // 0=exit, 1=one file, 2+ =many files
388 cfn = (Byte *) xstrdup(argv[optind]);
392 //-----------------------------------------------------------
397 static void edit_file(Byte * fn)
402 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
405 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
406 #ifdef CONFIG_FEATURE_VI_YANKMARK
407 static Byte *cur_line;
408 #endif /* CONFIG_FEATURE_VI_YANKMARK */
414 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
416 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
417 new_screen(rows, columns); // get memory for virtual screen
419 cnt = file_size(fn); // file size
420 size = 2 * cnt; // 200% of file size
421 new_text(size); // get a text[] buffer
422 screenbegin = dot = end = text;
424 ch= file_insert(fn, text, cnt);
427 (void) char_insert(text, '\n'); // start empty buf with dummy line
429 file_modified = FALSE;
430 #ifdef CONFIG_FEATURE_VI_YANKMARK
431 YDreg = 26; // default Yank/Delete reg
432 Ureg = 27; // hold orig line for "U" cmd
433 for (cnt = 0; cnt < 28; cnt++) {
436 mark[26] = mark[27] = text; // init "previous context"
437 #endif /* CONFIG_FEATURE_VI_YANKMARK */
439 err_method = 1; // flash
440 last_forward_char = last_input_char = '\0';
445 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
446 signal(SIGHUP, catch_sig);
447 signal(SIGINT, catch_sig);
448 signal(SIGALRM, alarm_sig);
449 signal(SIGTERM, catch_sig);
450 signal(SIGQUIT, core_sig);
451 signal(SIGILL, core_sig);
452 signal(SIGTRAP, core_sig);
453 signal(SIGIOT, core_sig);
454 signal(SIGABRT, core_sig);
455 signal(SIGFPE, core_sig);
456 signal(SIGBUS, core_sig);
457 signal(SIGSEGV, core_sig);
459 signal(SIGSYS, core_sig);
461 signal(SIGWINCH, winch_sig);
462 signal(SIGTSTP, suspend_sig);
463 sig = setjmp(restart);
467 msg = "(window resize)";
477 msg = "(I tried to touch invalid memory)";
481 psbs("-- caught signal %d %s--", sig, msg);
482 screenbegin = dot = text;
484 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
487 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
490 offset = 0; // no horizontal offset
492 #ifdef CONFIG_FEATURE_VI_DOT_CMD
493 if (last_modifying_cmd != 0)
494 free(last_modifying_cmd);
495 if (ioq_start != NULL)
497 ioq = ioq_start = last_modifying_cmd = 0;
499 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
500 redraw(FALSE); // dont force every col re-draw
503 //------This is the main Vi cmd handling loop -----------------------
504 while (editing > 0) {
505 #ifdef CONFIG_FEATURE_VI_CRASHME
507 if ((end - text) > 1) {
508 crash_dummy(); // generate a random command
512 string_insert(text, (Byte *) "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
516 #endif /* CONFIG_FEATURE_VI_CRASHME */
517 last_input_char = c = get_one_char(); // get a cmd from user
518 #ifdef CONFIG_FEATURE_VI_YANKMARK
519 // save a copy of the current line- for the 'U" command
520 if (begin_line(dot) != cur_line) {
521 cur_line = begin_line(dot);
522 text_yank(begin_line(dot), end_line(dot), Ureg);
524 #endif /* CONFIG_FEATURE_VI_YANKMARK */
525 #ifdef CONFIG_FEATURE_VI_DOT_CMD
526 // These are commands that change text[].
527 // Remember the input for the "." command
528 if (!adding2q && ioq_start == 0
529 && strchr((char *) modifying_cmds, c) != NULL) {
532 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
533 do_cmd(c); // execute the user command
535 // poll to see if there is input already waiting. if we are
536 // not able to display output fast enough to keep up, skip
537 // the display update until we catch up with input.
538 if (mysleep(0) == 0) {
539 // no input pending- so update output
543 #ifdef CONFIG_FEATURE_VI_CRASHME
545 crash_test(); // test editor variables
546 #endif /* CONFIG_FEATURE_VI_CRASHME */
548 //-------------------------------------------------------------------
550 place_cursor(rows, 0, FALSE); // go to bottom of screen
551 clear_to_eol(); // Erase to end of line
555 static Byte readbuffer[BUFSIZ];
557 #ifdef CONFIG_FEATURE_VI_CRASHME
558 static int totalcmds = 0;
559 static int Mp = 85; // Movement command Probability
560 static int Np = 90; // Non-movement command Probability
561 static int Dp = 96; // Delete command Probability
562 static int Ip = 97; // Insert command Probability
563 static int Yp = 98; // Yank command Probability
564 static int Pp = 99; // Put command Probability
565 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
566 char chars[20] = "\t012345 abcdABCD-=.$";
567 char *words[20] = { "this", "is", "a", "test",
568 "broadcast", "the", "emergency", "of",
569 "system", "quick", "brown", "fox",
570 "jumped", "over", "lazy", "dogs",
571 "back", "January", "Febuary", "March"
574 "You should have received a copy of the GNU General Public License\n",
575 "char c, cm, *cmd, *cmd1;\n",
576 "generate a command by percentages\n",
577 "Numbers may be typed as a prefix to some commands.\n",
578 "Quit, discarding changes!\n",
579 "Forced write, if permission originally not valid.\n",
580 "In general, any ex or ed command (such as substitute or delete).\n",
581 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
582 "Please get w/ me and I will go over it with you.\n",
583 "The following is a list of scheduled, committed changes.\n",
584 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
585 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
586 "Any question about transactions please contact Sterling Huxley.\n",
587 "I will try to get back to you by Friday, December 31.\n",
588 "This Change will be implemented on Friday.\n",
589 "Let me know if you have problems accessing this;\n",
590 "Sterling Huxley recently added you to the access list.\n",
591 "Would you like to go to lunch?\n",
592 "The last command will be automatically run.\n",
593 "This is too much english for a computer geek.\n",
595 char *multilines[20] = {
596 "You should have received a copy of the GNU General Public License\n",
597 "char c, cm, *cmd, *cmd1;\n",
598 "generate a command by percentages\n",
599 "Numbers may be typed as a prefix to some commands.\n",
600 "Quit, discarding changes!\n",
601 "Forced write, if permission originally not valid.\n",
602 "In general, any ex or ed command (such as substitute or delete).\n",
603 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
604 "Please get w/ me and I will go over it with you.\n",
605 "The following is a list of scheduled, committed changes.\n",
606 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
607 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
608 "Any question about transactions please contact Sterling Huxley.\n",
609 "I will try to get back to you by Friday, December 31.\n",
610 "This Change will be implemented on Friday.\n",
611 "Let me know if you have problems accessing this;\n",
612 "Sterling Huxley recently added you to the access list.\n",
613 "Would you like to go to lunch?\n",
614 "The last command will be automatically run.\n",
615 "This is too much english for a computer geek.\n",
618 // create a random command to execute
619 static void crash_dummy()
621 static int sleeptime; // how long to pause between commands
622 char c, cm, *cmd, *cmd1;
623 int i, cnt, thing, rbi, startrbi, percent;
625 // "dot" movement commands
626 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
628 // is there already a command running?
629 if (strlen((char *) readbuffer) > 0)
633 sleeptime = 0; // how long to pause between commands
634 memset(readbuffer, '\0', BUFSIZ - 1); // clear the read buffer
635 // generate a command by percentages
636 percent = (int) lrand48() % 100; // get a number from 0-99
637 if (percent < Mp) { // Movement commands
638 // available commands
641 } else if (percent < Np) { // non-movement commands
642 cmd = "mz<>\'\""; // available commands
644 } else if (percent < Dp) { // Delete commands
645 cmd = "dx"; // available commands
647 } else if (percent < Ip) { // Inset commands
648 cmd = "iIaAsrJ"; // available commands
650 } else if (percent < Yp) { // Yank commands
651 cmd = "yY"; // available commands
653 } else if (percent < Pp) { // Put commands
654 cmd = "pP"; // available commands
657 // We do not know how to handle this command, try again
661 // randomly pick one of the available cmds from "cmd[]"
662 i = (int) lrand48() % strlen(cmd);
664 if (strchr(":\024", cm))
665 goto cd0; // dont allow colon or ctrl-T commands
666 readbuffer[rbi++] = cm; // put cmd into input buffer
668 // now we have the command-
669 // there are 1, 2, and multi char commands
670 // find out which and generate the rest of command as necessary
671 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
672 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
673 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
674 cmd1 = "abcdefghijklmnopqrstuvwxyz";
676 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
678 readbuffer[rbi++] = c; // add movement to input buffer
680 if (strchr("iIaAsc", cm)) { // multi-char commands
683 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
685 readbuffer[rbi++] = c; // add movement to input buffer
687 thing = (int) lrand48() % 4; // what thing to insert
688 cnt = (int) lrand48() % 10; // how many to insert
689 for (i = 0; i < cnt; i++) {
690 if (thing == 0) { // insert chars
691 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
692 } else if (thing == 1) { // insert words
693 strcat((char *) readbuffer, words[(int) lrand48() % 20]);
694 strcat((char *) readbuffer, " ");
695 sleeptime = 0; // how fast to type
696 } else if (thing == 2) { // insert lines
697 strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
698 sleeptime = 0; // how fast to type
699 } else { // insert multi-lines
700 strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
701 sleeptime = 0; // how fast to type
704 strcat((char *) readbuffer, "\033");
709 (void) mysleep(sleeptime); // sleep 1/100 sec
712 // test to see if there are any errors
713 static void crash_test()
715 static time_t oldtim;
717 char d[2], buf[BUFSIZ], msg[BUFSIZ];
721 strcat((char *) msg, "end<text ");
724 strcat((char *) msg, "end>textend ");
727 strcat((char *) msg, "dot<text ");
730 strcat((char *) msg, "dot>end ");
732 if (screenbegin < text) {
733 strcat((char *) msg, "screenbegin<text ");
735 if (screenbegin > end - 1) {
736 strcat((char *) msg, "screenbegin>end-1 ");
739 if (strlen(msg) > 0) {
741 sprintf(buf, "\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
742 totalcmds, last_input_char, msg, SOs, SOn);
743 write(1, buf, strlen(buf));
744 while (read(0, d, 1) > 0) {
745 if (d[0] == '\n' || d[0] == '\r')
750 tim = (time_t) time((time_t *) 0);
751 if (tim >= (oldtim + 3)) {
752 sprintf((char *) status_buffer,
753 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
754 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
759 #endif /* CONFIG_FEATURE_VI_CRASHME */
761 //----- The Colon commands -------------------------------------
762 #ifdef CONFIG_FEATURE_VI_COLON
763 static Byte *get_one_address(Byte * p, int *addr) // get colon addr, if present
768 #ifdef CONFIG_FEATURE_VI_YANKMARK
770 #endif /* CONFIG_FEATURE_VI_YANKMARK */
771 #ifdef CONFIG_FEATURE_VI_SEARCH
772 Byte *pat, buf[BUFSIZ];
773 #endif /* CONFIG_FEATURE_VI_SEARCH */
775 *addr = -1; // assume no addr
776 if (*p == '.') { // the current line
779 *addr = count_lines(text, q);
780 #ifdef CONFIG_FEATURE_VI_YANKMARK
781 } else if (*p == '\'') { // is this a mark addr
785 if (c >= 'a' && c <= 'z') {
789 if (q != NULL) { // is mark valid
790 *addr = count_lines(text, q); // count lines
793 #endif /* CONFIG_FEATURE_VI_YANKMARK */
794 #ifdef CONFIG_FEATURE_VI_SEARCH
795 } else if (*p == '/') { // a search pattern
803 pat = (Byte *) xstrdup((char *) buf); // save copy of pattern
806 q = char_search(dot, pat, FORWARD, FULL);
808 *addr = count_lines(text, q);
811 #endif /* CONFIG_FEATURE_VI_SEARCH */
812 } else if (*p == '$') { // the last line in file
814 q = begin_line(end - 1);
815 *addr = count_lines(text, q);
816 } else if (isdigit(*p)) { // specific line number
817 sscanf((char *) p, "%d%n", addr, &st);
819 } else { // I don't reconise this
820 // unrecognised address- assume -1
826 static Byte *get_address(Byte *p, int *b, int *e) // get two colon addrs, if present
828 //----- get the address' i.e., 1,3 'a,'b -----
829 // get FIRST addr, if present
831 p++; // skip over leading spaces
832 if (*p == '%') { // alias for 1,$
835 *e = count_lines(text, end-1);
838 p = get_one_address(p, b);
841 if (*p == ',') { // is there a address seperator
845 // get SECOND addr, if present
846 p = get_one_address(p, e);
850 p++; // skip over trailing spaces
854 static void colon(Byte * buf)
856 Byte c, *orig_buf, *buf1, *q, *r;
857 Byte *fn, cmd[BUFSIZ], args[BUFSIZ];
858 int i, l, li, ch, st, b, e;
859 int useforce, forced;
862 // :3154 // if (-e line 3154) goto it else stay put
863 // :4,33w! foo // write a portion of buffer to file "foo"
864 // :w // write all of buffer to current file
866 // :q! // quit- dont care about modified file
867 // :'a,'z!sort -u // filter block through sort
868 // :'f // goto mark "f"
869 // :'fl // list literal the mark "f" line
870 // :.r bar // read file "bar" into buffer before dot
871 // :/123/,/abc/d // delete lines from "123" line to "abc" line
872 // :/xyz/ // goto the "xyz" line
873 // :s/find/replace/ // substitute pattern "find" with "replace"
874 // :!<cmd> // run <cmd> then return
876 if (strlen((char *) buf) <= 0)
879 buf++; // move past the ':'
881 forced = useforce = FALSE;
882 li = st = ch = i = 0;
884 q = text; // assume 1,$ for the range
886 li = count_lines(text, end - 1);
887 fn = cfn; // default to current file
888 memset(cmd, '\0', BUFSIZ); // clear cmd[]
889 memset(args, '\0', BUFSIZ); // clear args[]
891 // look for optional address(es) :. :1 :1,9 :'q,'a :%
892 buf = get_address(buf, &b, &e);
894 // remember orig command line
897 // get the COMMAND into cmd[]
899 while (*buf != '\0') {
907 strcpy((char *) args, (char *) buf);
908 buf1 = last_char_is((char *)cmd, '!');
911 *buf1 = '\0'; // get rid of !
914 // if there is only one addr, then the addr
915 // is the line number of the single line the
916 // user wants. So, reset the end
917 // pointer to point at end of the "b" line
918 q = find_line(b); // what line is #b
923 // we were given two addrs. change the
924 // end pointer to the addr given by user.
925 r = find_line(e); // what line is #e
929 // ------------ now look for the command ------------
930 i = strlen((char *) cmd);
931 if (i == 0) { // :123CR goto line #123
933 dot = find_line(b); // what line is #b
936 } else if (strncmp((char *) cmd, "!", 1) == 0) { // run a cmd
937 // :!ls run the <cmd>
938 (void) alarm(0); // wait for input- no alarms
939 place_cursor(rows - 1, 0, FALSE); // go to Status line
940 clear_to_eol(); // clear the line
942 system(orig_buf+1); // run the cmd
944 Hit_Return(); // let user see results
945 (void) alarm(3); // done waiting for input
946 } else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address
947 if (b < 0) { // no addr given- use defaults
948 b = e = count_lines(text, dot);
951 } else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines
952 if (b < 0) { // no addr given- use defaults
953 q = begin_line(dot); // assume .,. for the range
956 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
958 } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file
961 // don't edit, if the current file has been modified
962 if (file_modified && ! useforce) {
963 psbs("No write since last change (:edit! overrides)");
966 if (strlen(args) > 0) {
967 // the user supplied a file name
969 } else if (cfn != 0 && strlen(cfn) > 0) {
970 // no user supplied name- use the current filename
974 // no user file name, no current name- punt
975 psbs("No current filename");
979 // see if file exists- if not, its just a new file request
980 if ((sr=stat((char*)fn, &st_buf)) < 0) {
981 // This is just a request for a new file creation.
982 // The file_insert below will fail but we get
983 // an empty buffer with a file name. Then the "write"
984 // command can do the create.
986 if ((st_buf.st_mode & (S_IFREG)) == 0) {
987 // This is not a regular file
988 psbs("\"%s\" is not a regular file", fn);
991 if ((st_buf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
992 // dont have any read permissions
993 psbs("\"%s\" is not readable", fn);
998 // There is a read-able regular file
999 // make this the current file
1000 q = (Byte *) xstrdup((char *) fn); // save the cfn
1002 free(cfn); // free the old name
1003 cfn = q; // remember new cfn
1006 // delete all the contents of text[]
1007 new_text(2 * file_size(fn));
1008 screenbegin = dot = end = text;
1011 ch = file_insert(fn, text, file_size(fn));
1014 // start empty buf with dummy line
1015 (void) char_insert(text, '\n');
1018 file_modified = FALSE;
1019 #ifdef CONFIG_FEATURE_VI_YANKMARK
1020 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) {
1021 free(reg[Ureg]); // free orig line reg- for 'U'
1024 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) {
1025 free(reg[YDreg]); // free default yank/delete register
1028 for (li = 0; li < 28; li++) {
1031 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1032 // how many lines in text[]?
1033 li = count_lines(text, end - 1);
1035 #ifdef CONFIG_FEATURE_VI_READONLY
1037 #endif /* CONFIG_FEATURE_VI_READONLY */
1039 (sr < 0 ? " [New file]" : ""),
1040 #ifdef CONFIG_FEATURE_VI_READONLY
1041 ((vi_readonly || readonly) ? " [Read only]" : ""),
1042 #endif /* CONFIG_FEATURE_VI_READONLY */
1044 } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this
1045 if (b != -1 || e != -1) {
1046 ni((Byte *) "No address allowed on this command");
1049 if (strlen((char *) args) > 0) {
1050 // user wants a new filename
1053 cfn = (Byte *) xstrdup((char *) args);
1055 // user wants file status info
1058 } else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available
1059 // print out values of all features
1060 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
1061 clear_to_eol(); // clear the line
1066 } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line
1067 if (b < 0) { // no addr given- use defaults
1068 q = begin_line(dot); // assume .,. for the range
1071 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
1072 clear_to_eol(); // clear the line
1073 write(1, "\r\n", 2);
1074 for (; q <= r; q++) {
1080 } else if (*q < ' ') {
1088 #ifdef CONFIG_FEATURE_VI_SET
1090 #endif /* CONFIG_FEATURE_VI_SET */
1092 } else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit
1093 (strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file
1095 // force end of argv list
1102 // don't exit if the file been modified
1103 if (file_modified) {
1104 psbs("No write since last change (:%s! overrides)",
1105 (*cmd == 'q' ? "quit" : "next"));
1108 // are there other file to edit
1109 if (*cmd == 'q' && optind < save_argc - 1) {
1110 psbs("%d more file to edit", (save_argc - optind - 1));
1113 if (*cmd == 'n' && optind >= save_argc - 1) {
1114 psbs("No more files to edit");
1118 } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[]
1120 if (strlen((char *) fn) <= 0) {
1121 psbs("No filename given");
1124 if (b < 0) { // no addr given- use defaults
1125 q = begin_line(dot); // assume "dot"
1127 // read after current line- unless user said ":0r foo"
1130 #ifdef CONFIG_FEATURE_VI_READONLY
1131 l= readonly; // remember current files' status
1133 ch = file_insert(fn, q, file_size(fn));
1134 #ifdef CONFIG_FEATURE_VI_READONLY
1138 goto vc1; // nothing was inserted
1139 // how many lines in text[]?
1140 li = count_lines(q, q + ch - 1);
1142 #ifdef CONFIG_FEATURE_VI_READONLY
1144 #endif /* CONFIG_FEATURE_VI_READONLY */
1146 #ifdef CONFIG_FEATURE_VI_READONLY
1147 ((vi_readonly || readonly) ? " [Read only]" : ""),
1148 #endif /* CONFIG_FEATURE_VI_READONLY */
1151 // if the insert is before "dot" then we need to update
1154 file_modified = TRUE;
1156 } else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args
1157 if (file_modified && ! useforce) {
1158 psbs("No write since last change (:rewind! overrides)");
1160 // reset the filenames to edit
1161 optind = fn_start - 1;
1164 #ifdef CONFIG_FEATURE_VI_SET
1165 } else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features
1166 i = 0; // offset into args
1167 if (strlen((char *) args) == 0) {
1168 // print out values of all options
1169 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
1170 clear_to_eol(); // clear the line
1171 printf("----------------------------------------\r\n");
1172 #ifdef CONFIG_FEATURE_VI_SETOPTS
1175 printf("autoindent ");
1181 printf("ignorecase ");
1184 printf("showmatch ");
1185 printf("tabstop=%d ", tabstop);
1186 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1190 if (strncasecmp((char *) args, "no", 2) == 0)
1191 i = 2; // ":set noautoindent"
1192 #ifdef CONFIG_FEATURE_VI_SETOPTS
1193 if (strncasecmp((char *) args + i, "autoindent", 10) == 0 ||
1194 strncasecmp((char *) args + i, "ai", 2) == 0) {
1195 autoindent = (i == 2) ? 0 : 1;
1197 if (strncasecmp((char *) args + i, "flash", 5) == 0 ||
1198 strncasecmp((char *) args + i, "fl", 2) == 0) {
1199 err_method = (i == 2) ? 0 : 1;
1201 if (strncasecmp((char *) args + i, "ignorecase", 10) == 0 ||
1202 strncasecmp((char *) args + i, "ic", 2) == 0) {
1203 ignorecase = (i == 2) ? 0 : 1;
1205 if (strncasecmp((char *) args + i, "showmatch", 9) == 0 ||
1206 strncasecmp((char *) args + i, "sm", 2) == 0) {
1207 showmatch = (i == 2) ? 0 : 1;
1209 if (strncasecmp((char *) args + i, "tabstop", 7) == 0) {
1210 sscanf(strchr((char *) args + i, '='), "=%d", &ch);
1211 if (ch > 0 && ch < columns - 1)
1214 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1215 #endif /* CONFIG_FEATURE_VI_SET */
1216 #ifdef CONFIG_FEATURE_VI_SEARCH
1217 } else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
1221 // F points to the "find" pattern
1222 // R points to the "replace" pattern
1223 // replace the cmd line delimiters "/" with NULLs
1224 gflag = 0; // global replace flag
1225 c = orig_buf[1]; // what is the delimiter
1226 F = orig_buf + 2; // start of "find"
1227 R = (Byte *) strchr((char *) F, c); // middle delimiter
1228 if (!R) goto colon_s_fail;
1229 *R++ = '\0'; // terminate "find"
1230 buf1 = (Byte *) strchr((char *) R, c);
1231 if (!buf1) goto colon_s_fail;
1232 *buf1++ = '\0'; // terminate "replace"
1233 if (*buf1 == 'g') { // :s/foo/bar/g
1235 gflag++; // turn on gflag
1238 if (b < 0) { // maybe :s/foo/bar/
1239 q = begin_line(dot); // start with cur line
1240 b = count_lines(text, q); // cur line number
1243 e = b; // maybe :.s/foo/bar/
1244 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1245 ls = q; // orig line start
1247 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1249 // we found the "find" pattern- delete it
1250 (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1);
1251 // inset the "replace" patern
1252 (void) string_insert(buf1, R); // insert the string
1253 // check for "global" :s/foo/bar/g
1255 if ((buf1 + strlen((char *) R)) < end_line(ls)) {
1256 q = buf1 + strlen((char *) R);
1257 goto vc4; // don't let q move past cur line
1263 #endif /* CONFIG_FEATURE_VI_SEARCH */
1264 } else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version
1265 psb("%s", vi_Version);
1266 } else if ((strncasecmp((char *) cmd, "write", i) == 0) || // write text to file
1267 (strncasecmp((char *) cmd, "wq", i) == 0) ||
1268 (strncasecmp((char *) cmd, "x", i) == 0)) {
1269 // is there a file name to write to?
1270 if (strlen((char *) args) > 0) {
1273 #ifdef CONFIG_FEATURE_VI_READONLY
1274 if ((vi_readonly || readonly) && ! useforce) {
1275 psbs("\"%s\" File is read only", fn);
1278 #endif /* CONFIG_FEATURE_VI_READONLY */
1279 // how many lines in text[]?
1280 li = count_lines(q, r);
1282 // see if file exists- if not, its just a new file request
1284 // if "fn" is not write-able, chmod u+w
1285 // sprintf(syscmd, "chmod u+w %s", fn);
1289 l = file_write(fn, q, r);
1290 if (useforce && forced) {
1292 // sprintf(syscmd, "chmod u-w %s", fn);
1296 psb("\"%s\" %dL, %dC", fn, li, l);
1297 if (q == text && r == end - 1 && l == ch)
1298 file_modified = FALSE;
1299 if ((cmd[0] == 'x' || cmd[1] == 'q') && l == ch) {
1302 #ifdef CONFIG_FEATURE_VI_READONLY
1304 #endif /* CONFIG_FEATURE_VI_READONLY */
1305 #ifdef CONFIG_FEATURE_VI_YANKMARK
1306 } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines
1307 if (b < 0) { // no addr given- use defaults
1308 q = begin_line(dot); // assume .,. for the range
1311 text_yank(q, r, YDreg);
1312 li = count_lines(q, r);
1313 psb("Yank %d lines (%d chars) into [%c]",
1314 li, strlen((char *) reg[YDreg]), what_reg());
1315 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1321 dot = bound_dot(dot); // make sure "dot" is valid
1323 #ifdef CONFIG_FEATURE_VI_SEARCH
1325 psb(":s expression missing delimiters");
1331 static void Hit_Return(void)
1335 standout_start(); // start reverse video
1336 write(1, "[Hit return to continue]", 24);
1337 standout_end(); // end reverse video
1338 while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */
1340 redraw(TRUE); // force redraw all
1342 #endif /* CONFIG_FEATURE_VI_COLON */
1344 //----- Synchronize the cursor to Dot --------------------------
1345 static void sync_cursor(Byte * d, int *row, int *col)
1347 Byte *beg_cur, *end_cur; // begin and end of "d" line
1348 Byte *beg_scr, *end_scr; // begin and end of screen
1352 beg_cur = begin_line(d); // first char of cur line
1353 end_cur = end_line(d); // last char of cur line
1355 beg_scr = end_scr = screenbegin; // first char of screen
1356 end_scr = end_screen(); // last char of screen
1358 if (beg_cur < screenbegin) {
1359 // "d" is before top line on screen
1360 // how many lines do we have to move
1361 cnt = count_lines(beg_cur, screenbegin);
1363 screenbegin = beg_cur;
1364 if (cnt > (rows - 1) / 2) {
1365 // we moved too many lines. put "dot" in middle of screen
1366 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1367 screenbegin = prev_line(screenbegin);
1370 } else if (beg_cur > end_scr) {
1371 // "d" is after bottom line on screen
1372 // how many lines do we have to move
1373 cnt = count_lines(end_scr, beg_cur);
1374 if (cnt > (rows - 1) / 2)
1375 goto sc1; // too many lines
1376 for (ro = 0; ro < cnt - 1; ro++) {
1377 // move screen begin the same amount
1378 screenbegin = next_line(screenbegin);
1379 // now, move the end of screen
1380 end_scr = next_line(end_scr);
1381 end_scr = end_line(end_scr);
1384 // "d" is on screen- find out which row
1386 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1392 // find out what col "d" is on
1394 do { // drive "co" to correct column
1395 if (*tp == '\n' || *tp == '\0')
1399 co += ((tabstop - 1) - (co % tabstop));
1400 } else if (*tp < ' ') {
1401 co++; // display as ^X, use 2 columns
1403 } while (tp++ < d && ++co);
1405 // "co" is the column where "dot" is.
1406 // The screen has "columns" columns.
1407 // The currently displayed columns are 0+offset -- columns+ofset
1408 // |-------------------------------------------------------------|
1410 // offset | |------- columns ----------------|
1412 // If "co" is already in this range then we do not have to adjust offset
1413 // but, we do have to subtract the "offset" bias from "co".
1414 // If "co" is outside this range then we have to change "offset".
1415 // If the first char of a line is a tab the cursor will try to stay
1416 // in column 7, but we have to set offset to 0.
1418 if (co < 0 + offset) {
1421 if (co >= columns + offset) {
1422 offset = co - columns + 1;
1424 // if the first char of the line is a tab, and "dot" is sitting on it
1425 // force offset to 0.
1426 if (d == beg_cur && *d == '\t') {
1435 //----- Text Movement Routines ---------------------------------
1436 static Byte *begin_line(Byte * p) // return pointer to first char cur line
1438 while (p > text && p[-1] != '\n')
1439 p--; // go to cur line B-o-l
1443 static Byte *end_line(Byte * p) // return pointer to NL of cur line line
1445 while (p < end - 1 && *p != '\n')
1446 p++; // go to cur line E-o-l
1450 extern inline Byte *dollar_line(Byte * p) // return pointer to just before NL line
1452 while (p < end - 1 && *p != '\n')
1453 p++; // go to cur line E-o-l
1454 // Try to stay off of the Newline
1455 if (*p == '\n' && (p - begin_line(p)) > 0)
1460 static Byte *prev_line(Byte * p) // return pointer first char prev line
1462 p = begin_line(p); // goto begining of cur line
1463 if (p[-1] == '\n' && p > text)
1464 p--; // step to prev line
1465 p = begin_line(p); // goto begining of prev line
1469 static Byte *next_line(Byte * p) // return pointer first char next line
1472 if (*p == '\n' && p < end - 1)
1473 p++; // step to next line
1477 //----- Text Information Routines ------------------------------
1478 static Byte *end_screen(void)
1483 // find new bottom line
1485 for (cnt = 0; cnt < rows - 2; cnt++)
1491 static int count_lines(Byte * start, Byte * stop) // count line from start to stop
1496 if (stop < start) { // start and stop are backwards- reverse them
1502 stop = end_line(stop); // get to end of this line
1503 for (q = start; q <= stop && q <= end - 1; q++) {
1510 static Byte *find_line(int li) // find begining of line #li
1514 for (q = text; li > 1; li--) {
1520 //----- Dot Movement Routines ----------------------------------
1521 static void dot_left(void)
1523 if (dot > text && dot[-1] != '\n')
1527 static void dot_right(void)
1529 if (dot < end - 1 && *dot != '\n')
1533 static void dot_begin(void)
1535 dot = begin_line(dot); // return pointer to first char cur line
1538 static void dot_end(void)
1540 dot = end_line(dot); // return pointer to last char cur line
1543 static Byte *move_to_col(Byte * p, int l)
1550 if (*p == '\n' || *p == '\0')
1554 co += ((tabstop - 1) - (co % tabstop));
1555 } else if (*p < ' ') {
1556 co++; // display as ^X, use 2 columns
1558 } while (++co <= l && p++ < end);
1562 static void dot_next(void)
1564 dot = next_line(dot);
1567 static void dot_prev(void)
1569 dot = prev_line(dot);
1572 static void dot_scroll(int cnt, int dir)
1576 for (; cnt > 0; cnt--) {
1579 // ctrl-Y scroll up one line
1580 screenbegin = prev_line(screenbegin);
1583 // ctrl-E scroll down one line
1584 screenbegin = next_line(screenbegin);
1587 // make sure "dot" stays on the screen so we dont scroll off
1588 if (dot < screenbegin)
1590 q = end_screen(); // find new bottom line
1592 dot = begin_line(q); // is dot is below bottom line?
1596 static void dot_skip_over_ws(void)
1599 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1603 static void dot_delete(void) // delete the char at 'dot'
1605 (void) text_hole_delete(dot, dot);
1608 static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end"
1610 if (p >= end && end > text) {
1612 indicate_error('1');
1616 indicate_error('2');
1621 //----- Helper Utility Routines --------------------------------
1623 //----------------------------------------------------------------
1624 //----- Char Routines --------------------------------------------
1625 /* Chars that are part of a word-
1626 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1627 * Chars that are Not part of a word (stoppers)
1628 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1629 * Chars that are WhiteSpace
1630 * TAB NEWLINE VT FF RETURN SPACE
1631 * DO NOT COUNT NEWLINE AS WHITESPACE
1634 static Byte *new_screen(int ro, int co)
1640 screensize = ro * co + 8;
1641 screen = (Byte *) xmalloc(screensize);
1642 // initialize the new screen. assume this will be a empty file.
1644 // non-existant text[] lines start with a tilde (~).
1645 for (li = 1; li < ro - 1; li++) {
1646 screen[(li * co) + 0] = '~';
1651 static Byte *new_text(int size)
1654 size = 10240; // have a minimum size for new files
1659 text = (Byte *) xmalloc(size + 8);
1660 memset(text, '\0', size); // clear new text[]
1661 //text += 4; // leave some room for "oops"
1662 textend = text + size - 1;
1663 //textend -= 4; // leave some root for "oops"
1667 #ifdef CONFIG_FEATURE_VI_SEARCH
1668 static int mycmp(Byte * s1, Byte * s2, int len)
1672 i = strncmp((char *) s1, (char *) s2, len);
1673 #ifdef CONFIG_FEATURE_VI_SETOPTS
1675 i = strncasecmp((char *) s1, (char *) s2, len);
1677 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1681 static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p
1683 #ifndef REGEX_SEARCH
1687 len = strlen((char *) pat);
1688 if (dir == FORWARD) {
1689 stop = end - 1; // assume range is p - end-1
1690 if (range == LIMITED)
1691 stop = next_line(p); // range is to next line
1692 for (start = p; start < stop; start++) {
1693 if (mycmp(start, pat, len) == 0) {
1697 } else if (dir == BACK) {
1698 stop = text; // assume range is text - p
1699 if (range == LIMITED)
1700 stop = prev_line(p); // range is to prev line
1701 for (start = p - len; start >= stop; start--) {
1702 if (mycmp(start, pat, len) == 0) {
1707 // pattern not found
1709 #else /*REGEX_SEARCH */
1711 struct re_pattern_buffer preg;
1715 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1721 // assume a LIMITED forward search
1729 // count the number of chars to search over, forward or backward
1733 // RANGE could be negative if we are searching backwards
1736 q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
1738 // The pattern was not compiled
1739 psbs("bad search pattern: \"%s\": %s", pat, q);
1740 i = 0; // return p if pattern not compiled
1750 // search for the compiled pattern, preg, in p[]
1751 // range < 0- search backward
1752 // range > 0- search forward
1754 // re_search() < 0 not found or error
1755 // re_search() > 0 index of found pattern
1756 // struct pattern char int int int struct reg
1757 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1758 i = re_search(&preg, q, size, 0, range, 0);
1761 i = 0; // return NULL if pattern not found
1764 if (dir == FORWARD) {
1770 #endif /*REGEX_SEARCH */
1772 #endif /* CONFIG_FEATURE_VI_SEARCH */
1774 static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
1776 if (c == 22) { // Is this an ctrl-V?
1777 p = stupid_insert(p, '^'); // use ^ to indicate literal next
1778 p--; // backup onto ^
1779 refresh(FALSE); // show the ^
1783 file_modified = TRUE; // has the file been modified
1784 } else if (c == 27) { // Is this an ESC?
1787 end_cmd_q(); // stop adding to q
1788 strcpy((char *) status_buffer, " "); // clear the status buffer
1789 if ((p[-1] != '\n') && (dot>text)) {
1792 } else if (c == erase_char) { // Is this a BS
1794 if ((p[-1] != '\n') && (dot>text)) {
1796 p = text_hole_delete(p, p); // shrink buffer 1 char
1797 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1798 // also rmove char from last_modifying_cmd
1799 if (strlen((char *) last_modifying_cmd) > 0) {
1802 q = last_modifying_cmd;
1803 q[strlen((char *) q) - 1] = '\0'; // erase BS
1804 q[strlen((char *) q) - 1] = '\0'; // erase prev char
1806 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
1809 // insert a char into text[]
1810 Byte *sp; // "save p"
1813 c = '\n'; // translate \r to \n
1814 sp = p; // remember addr of insert
1815 p = stupid_insert(p, c); // insert the char
1816 #ifdef CONFIG_FEATURE_VI_SETOPTS
1817 if (showmatch && strchr(")]}", *sp) != NULL) {
1820 if (autoindent && c == '\n') { // auto indent the new line
1823 q = prev_line(p); // use prev line as templet
1824 for (; isblnk(*q); q++) {
1825 p = stupid_insert(p, *q); // insert the char
1828 #endif /* CONFIG_FEATURE_VI_SETOPTS */
1833 static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
1835 p = text_hole_make(p, 1);
1838 file_modified = TRUE; // has the file been modified
1844 static Byte find_range(Byte ** start, Byte ** stop, Byte c)
1846 Byte *save_dot, *p, *q;
1852 if (strchr("cdy><", c)) {
1853 // these cmds operate on whole lines
1854 p = q = begin_line(p);
1855 for (cnt = 1; cnt < cmdcnt; cnt++) {
1859 } else if (strchr("^%$0bBeEft", c)) {
1860 // These cmds operate on char positions
1861 do_cmd(c); // execute movement cmd
1863 } else if (strchr("wW", c)) {
1864 do_cmd(c); // execute movement cmd
1866 dot--; // move back off of next word
1867 if (dot > text && *dot == '\n')
1868 dot--; // stay off NL
1870 } else if (strchr("H-k{", c)) {
1871 // these operate on multi-lines backwards
1872 q = end_line(dot); // find NL
1873 do_cmd(c); // execute movement cmd
1876 } else if (strchr("L+j}\r\n", c)) {
1877 // these operate on multi-lines forwards
1878 p = begin_line(dot);
1879 do_cmd(c); // execute movement cmd
1880 dot_end(); // find NL
1883 c = 27; // error- return an ESC char
1896 static int st_test(Byte * p, int type, int dir, Byte * tested)
1906 if (type == S_BEFORE_WS) {
1908 test = ((!isspace(c)) || c == '\n');
1910 if (type == S_TO_WS) {
1912 test = ((!isspace(c)) || c == '\n');
1914 if (type == S_OVER_WS) {
1916 test = ((isspace(c)));
1918 if (type == S_END_PUNCT) {
1920 test = ((ispunct(c)));
1922 if (type == S_END_ALNUM) {
1924 test = ((isalnum(c)) || c == '_');
1930 static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
1934 while (st_test(p, type, dir, &c)) {
1935 // make sure we limit search to correct number of lines
1936 if (c == '\n' && --linecnt < 1)
1938 if (dir >= 0 && p >= end - 1)
1940 if (dir < 0 && p <= text)
1942 p += dir; // move to next char
1947 // find matching char of pair () [] {}
1948 static Byte *find_pair(Byte * p, Byte c)
1955 dir = 1; // assume forward
1979 for (q = p + dir; text <= q && q < end; q += dir) {
1980 // look for match, count levels of pairs (( ))
1982 level++; // increase pair levels
1984 level--; // reduce pair level
1986 break; // found matching pair
1989 q = NULL; // indicate no match
1993 #ifdef CONFIG_FEATURE_VI_SETOPTS
1994 // show the matching char of a pair, () [] {}
1995 static void showmatching(Byte * p)
1999 // we found half of a pair
2000 q = find_pair(p, *p); // get loc of matching char
2002 indicate_error('3'); // no matching char
2004 // "q" now points to matching pair
2005 save_dot = dot; // remember where we are
2006 dot = q; // go to new loc
2007 refresh(FALSE); // let the user see it
2008 (void) mysleep(40); // give user some time
2009 dot = save_dot; // go back to old loc
2013 #endif /* CONFIG_FEATURE_VI_SETOPTS */
2015 // open a hole in text[]
2016 static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
2025 cnt = end - src; // the rest of buffer
2026 if (memmove(dest, src, cnt) != dest) {
2027 psbs("can't create room for new characters");
2029 memset(p, ' ', size); // clear new hole
2030 end = end + size; // adjust the new END
2031 file_modified = TRUE; // has the file been modified
2036 // close a hole in text[]
2037 static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
2042 // move forwards, from beginning
2046 if (q < p) { // they are backward- swap them
2050 hole_size = q - p + 1;
2052 if (src < text || src > end)
2054 if (dest < text || dest >= end)
2057 goto thd_atend; // just delete the end of the buffer
2058 if (memmove(dest, src, cnt) != dest) {
2059 psbs("can't delete the character");
2062 end = end - hole_size; // adjust the new END
2064 dest = end - 1; // make sure dest in below end-1
2066 dest = end = text; // keep pointers valid
2067 file_modified = TRUE; // has the file been modified
2072 // copy text into register, then delete text.
2073 // if dist <= 0, do not include, or go past, a NewLine
2075 static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
2079 // make sure start <= stop
2081 // they are backwards, reverse them
2087 // we can not cross NL boundaries
2091 // dont go past a NewLine
2092 for (; p + 1 <= stop; p++) {
2094 stop = p; // "stop" just before NewLine
2100 #ifdef CONFIG_FEATURE_VI_YANKMARK
2101 text_yank(start, stop, YDreg);
2102 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2103 if (yf == YANKDEL) {
2104 p = text_hole_delete(start, stop);
2109 static void show_help(void)
2111 puts("These features are available:"
2112 #ifdef CONFIG_FEATURE_VI_SEARCH
2113 "\n\tPattern searches with / and ?"
2114 #endif /* CONFIG_FEATURE_VI_SEARCH */
2115 #ifdef CONFIG_FEATURE_VI_DOT_CMD
2116 "\n\tLast command repeat with \'.\'"
2117 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
2118 #ifdef CONFIG_FEATURE_VI_YANKMARK
2119 "\n\tLine marking with 'x"
2120 "\n\tNamed buffers with \"x"
2121 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2122 #ifdef CONFIG_FEATURE_VI_READONLY
2123 "\n\tReadonly if vi is called as \"view\""
2124 "\n\tReadonly with -R command line arg"
2125 #endif /* CONFIG_FEATURE_VI_READONLY */
2126 #ifdef CONFIG_FEATURE_VI_SET
2127 "\n\tSome colon mode commands with \':\'"
2128 #endif /* CONFIG_FEATURE_VI_SET */
2129 #ifdef CONFIG_FEATURE_VI_SETOPTS
2130 "\n\tSettable options with \":set\""
2131 #endif /* CONFIG_FEATURE_VI_SETOPTS */
2132 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
2133 "\n\tSignal catching- ^C"
2134 "\n\tJob suspend and resume with ^Z"
2135 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
2136 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
2137 "\n\tAdapt to window re-sizes"
2138 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
2142 extern inline void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
2147 strcpy((char *) buf, ""); // init buf
2148 if (strlen((char *) s) <= 0)
2149 s = (Byte *) "(NULL)";
2150 for (; *s > '\0'; s++) {
2153 strcat((char *) buf, SOs);
2157 strcat((char *) buf, "^");
2161 strcat((char *) buf, (char *) b);
2163 strcat((char *) buf, SOn);
2165 strcat((char *) buf, "$");
2170 #ifdef CONFIG_FEATURE_VI_DOT_CMD
2171 static void start_new_cmd_q(Byte c)
2174 if (last_modifying_cmd != 0)
2175 free(last_modifying_cmd);
2176 // get buffer for new cmd
2177 last_modifying_cmd = (Byte *) xmalloc(BUFSIZ);
2178 memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue
2179 // if there is a current cmd count put it in the buffer first
2181 sprintf((char *) last_modifying_cmd, "%d", cmdcnt);
2182 // save char c onto queue
2183 last_modifying_cmd[strlen((char *) last_modifying_cmd)] = c;
2188 static void end_cmd_q(void)
2190 #ifdef CONFIG_FEATURE_VI_YANKMARK
2191 YDreg = 26; // go back to default Yank/Delete reg
2192 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2196 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
2198 #if defined(CONFIG_FEATURE_VI_YANKMARK) || defined(CONFIG_FEATURE_VI_COLON) || defined(CONFIG_FEATURE_VI_CRASHME)
2199 static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
2203 i = strlen((char *) s);
2204 p = text_hole_make(p, i);
2205 strncpy((char *) p, (char *) s, i);
2206 for (cnt = 0; *s != '\0'; s++) {
2210 #ifdef CONFIG_FEATURE_VI_YANKMARK
2211 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2212 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2215 #endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */
2217 #ifdef CONFIG_FEATURE_VI_YANKMARK
2218 static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register
2223 if (q < p) { // they are backwards- reverse them
2230 if (t != 0) { // if already a yank register
2233 t = (Byte *) xmalloc(cnt + 1); // get a new register
2234 memset(t, '\0', cnt + 1); // clear new text[]
2235 strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer
2240 static Byte what_reg(void)
2246 c = 'D'; // default to D-reg
2247 if (0 <= YDreg && YDreg <= 25)
2248 c = 'a' + (Byte) YDreg;
2256 static void check_context(Byte cmd)
2258 // A context is defined to be "modifying text"
2259 // Any modifying command establishes a new context.
2261 if (dot < context_start || dot > context_end) {
2262 if (strchr((char *) modifying_cmds, cmd) != NULL) {
2263 // we are trying to modify text[]- make this the current context
2264 mark[27] = mark[26]; // move cur to prev
2265 mark[26] = dot; // move local to cur
2266 context_start = prev_line(prev_line(dot));
2267 context_end = next_line(next_line(dot));
2268 //loiter= start_loiter= now;
2274 extern inline Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
2278 // the current context is in mark[26]
2279 // the previous context is in mark[27]
2280 // only swap context if other context is valid
2281 if (text <= mark[27] && mark[27] <= end - 1) {
2283 mark[27] = mark[26];
2285 p = mark[26]; // where we are going- previous context
2286 context_start = prev_line(prev_line(prev_line(p)));
2287 context_end = next_line(next_line(next_line(p)));
2291 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2293 static int isblnk(Byte c) // is the char a blank or tab
2295 return (c == ' ' || c == '\t');
2298 //----- Set terminal attributes --------------------------------
2299 static void rawmode(void)
2301 tcgetattr(0, &term_orig);
2302 term_vi = term_orig;
2303 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2304 term_vi.c_iflag &= (~IXON & ~ICRNL);
2305 term_vi.c_oflag &= (~ONLCR);
2307 term_vi.c_cc[VMIN] = 1;
2308 term_vi.c_cc[VTIME] = 0;
2310 erase_char = term_vi.c_cc[VERASE];
2311 tcsetattr(0, TCSANOW, &term_vi);
2314 static void cookmode(void)
2316 tcsetattr(0, TCSANOW, &term_orig);
2319 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
2320 //----- See what the window size currently is --------------------
2321 static void window_size_get(int sig)
2325 i = ioctl(0, TIOCGWINSZ, &winsize);
2328 winsize.ws_row = 24;
2329 winsize.ws_col = 80;
2331 if (winsize.ws_row <= 1) {
2332 winsize.ws_row = 24;
2334 if (winsize.ws_col <= 1) {
2335 winsize.ws_col = 80;
2337 rows = (int) winsize.ws_row;
2338 columns = (int) winsize.ws_col;
2340 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
2342 //----- Come here when we get a window resize signal ---------
2343 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
2344 static void winch_sig(int sig)
2346 signal(SIGWINCH, winch_sig);
2347 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
2349 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
2350 new_screen(rows, columns); // get memory for virtual screen
2351 redraw(TRUE); // re-draw the screen
2354 //----- Come here when we get a continue signal -------------------
2355 static void cont_sig(int sig)
2357 rawmode(); // terminal to "raw"
2358 *status_buffer = '\0'; // clear the status buffer
2359 redraw(TRUE); // re-draw the screen
2361 signal(SIGTSTP, suspend_sig);
2362 signal(SIGCONT, SIG_DFL);
2363 kill(getpid(), SIGCONT);
2366 //----- Come here when we get a Suspend signal -------------------
2367 static void suspend_sig(int sig)
2369 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2370 clear_to_eol(); // Erase to end of line
2371 cookmode(); // terminal to "cooked"
2373 signal(SIGCONT, cont_sig);
2374 signal(SIGTSTP, SIG_DFL);
2375 kill(getpid(), SIGTSTP);
2378 //----- Come here when we get a signal ---------------------------
2379 static void catch_sig(int sig)
2381 signal(SIGHUP, catch_sig);
2382 signal(SIGINT, catch_sig);
2383 signal(SIGTERM, catch_sig);
2384 longjmp(restart, sig);
2387 static void alarm_sig(int sig)
2389 signal(SIGALRM, catch_sig);
2390 longjmp(restart, sig);
2393 //----- Come here when we get a core dump signal -----------------
2394 static void core_sig(int sig)
2396 signal(SIGQUIT, core_sig);
2397 signal(SIGILL, core_sig);
2398 signal(SIGTRAP, core_sig);
2399 signal(SIGIOT, core_sig);
2400 signal(SIGABRT, core_sig);
2401 signal(SIGFPE, core_sig);
2402 signal(SIGBUS, core_sig);
2403 signal(SIGSEGV, core_sig);
2405 signal(SIGSYS, core_sig);
2408 dot = bound_dot(dot); // make sure "dot" is valid
2410 longjmp(restart, sig);
2412 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
2414 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
2416 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
2420 tv.tv_usec = hund * 10000;
2421 select(1, &rfds, NULL, NULL, &tv);
2422 return (FD_ISSET(0, &rfds));
2425 //----- IO Routines --------------------------------------------
2426 static Byte readit(void) // read (maybe cursor) key from stdin
2429 int i, bufsiz, cnt, cmdindex;
2435 static struct esc_cmds esccmds[] = {
2436 {(Byte *) "
\eOA", (Byte) VI_K_UP}, // cursor key Up
2437 {(Byte *) "
\eOB", (Byte) VI_K_DOWN}, // cursor key Down
2438 {(Byte *) "
\eOC", (Byte) VI_K_RIGHT}, // Cursor Key Right
2439 {(Byte *) "
\eOD", (Byte) VI_K_LEFT}, // cursor key Left
2440 {(Byte *) "
\eOH", (Byte) VI_K_HOME}, // Cursor Key Home
2441 {(Byte *) "
\eOF", (Byte) VI_K_END}, // Cursor Key End
2442 {(Byte *) "
\e[A", (Byte) VI_K_UP}, // cursor key Up
2443 {(Byte *) "
\e[B", (Byte) VI_K_DOWN}, // cursor key Down
2444 {(Byte *) "
\e[C", (Byte) VI_K_RIGHT}, // Cursor Key Right
2445 {(Byte *) "
\e[D", (Byte) VI_K_LEFT}, // cursor key Left
2446 {(Byte *) "
\e[H", (Byte) VI_K_HOME}, // Cursor Key Home
2447 {(Byte *) "
\e[F", (Byte) VI_K_END}, // Cursor Key End
2448 {(Byte *) "
\e[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert
2449 {(Byte *) "
\e[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up
2450 {(Byte *) "
\e[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down
2451 {(Byte *) "
\eOP", (Byte) VI_K_FUN1}, // Function Key F1
2452 {(Byte *) "
\eOQ", (Byte) VI_K_FUN2}, // Function Key F2
2453 {(Byte *) "
\eOR", (Byte) VI_K_FUN3}, // Function Key F3
2454 {(Byte *) "
\eOS", (Byte) VI_K_FUN4}, // Function Key F4
2455 {(Byte *) "
\e[15~", (Byte) VI_K_FUN5}, // Function Key F5
2456 {(Byte *) "
\e[17~", (Byte) VI_K_FUN6}, // Function Key F6
2457 {(Byte *) "
\e[18~", (Byte) VI_K_FUN7}, // Function Key F7
2458 {(Byte *) "
\e[19~", (Byte) VI_K_FUN8}, // Function Key F8
2459 {(Byte *) "
\e[20~", (Byte) VI_K_FUN9}, // Function Key F9
2460 {(Byte *) "
\e[21~", (Byte) VI_K_FUN10}, // Function Key F10
2461 {(Byte *) "
\e[23~", (Byte) VI_K_FUN11}, // Function Key F11
2462 {(Byte *) "
\e[24~", (Byte) VI_K_FUN12}, // Function Key F12
2463 {(Byte *) "
\e[11~", (Byte) VI_K_FUN1}, // Function Key F1
2464 {(Byte *) "
\e[12~", (Byte) VI_K_FUN2}, // Function Key F2
2465 {(Byte *) "
\e[13~", (Byte) VI_K_FUN3}, // Function Key F3
2466 {(Byte *) "
\e[14~", (Byte) VI_K_FUN4}, // Function Key F4
2469 #define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
2471 (void) alarm(0); // turn alarm OFF while we wait for input
2472 // get input from User- are there already input chars in Q?
2473 bufsiz = strlen((char *) readbuffer);
2476 // the Q is empty, wait for a typed char
2477 bufsiz = read(0, readbuffer, BUFSIZ - 1);
2480 goto ri0; // interrupted sys call
2483 if (errno == EFAULT)
2485 if (errno == EINVAL)
2492 readbuffer[bufsiz] = '\0';
2494 // return char if it is not part of ESC sequence
2495 if (readbuffer[0] != 27)
2498 // This is an ESC char. Is this Esc sequence?
2499 // Could be bare Esc key. See if there are any
2500 // more chars to read after the ESC. This would
2501 // be a Function or Cursor Key sequence.
2505 tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000
2507 // keep reading while there are input chars and room in buffer
2508 while (select(1, &rfds, NULL, NULL, &tv) > 0 && bufsiz <= (BUFSIZ - 5)) {
2509 // read the rest of the ESC string
2510 i = read(0, (void *) (readbuffer + bufsiz), BUFSIZ - bufsiz);
2513 readbuffer[bufsiz] = '\0'; // Terminate the string
2516 // Maybe cursor or function key?
2517 for (cmdindex = 0; cmdindex < ESCCMDS_COUNT; cmdindex++) {
2518 cnt = strlen((char *) esccmds[cmdindex].seq);
2519 i = strncmp((char *) esccmds[cmdindex].seq, (char *) readbuffer, cnt);
2521 // is a Cursor key- put derived value back into Q
2522 readbuffer[0] = esccmds[cmdindex].val;
2523 // squeeze out the ESC sequence
2524 for (i = 1; i < cnt; i++) {
2525 memmove(readbuffer + 1, readbuffer + 2, BUFSIZ - 2);
2526 readbuffer[BUFSIZ - 1] = '\0';
2533 // remove one char from Q
2534 memmove(readbuffer, readbuffer + 1, BUFSIZ - 1);
2535 readbuffer[BUFSIZ - 1] = '\0';
2536 (void) alarm(3); // we are done waiting for input, turn alarm ON
2540 //----- IO Routines --------------------------------------------
2541 static Byte get_one_char()
2545 #ifdef CONFIG_FEATURE_VI_DOT_CMD
2546 // ! adding2q && ioq == 0 read()
2547 // ! adding2q && ioq != 0 *ioq
2548 // adding2q *last_modifying_cmd= read()
2550 // we are not adding to the q.
2551 // but, we may be reading from a q
2553 // there is no current q, read from STDIN
2554 c = readit(); // get the users input
2556 // there is a queue to get chars from first
2559 // the end of the q, read from STDIN
2561 ioq_start = ioq = 0;
2562 c = readit(); // get the users input
2566 // adding STDIN chars to q
2567 c = readit(); // get the users input
2568 if (last_modifying_cmd != 0) {
2569 int len = strlen((char *) last_modifying_cmd);
2570 if (len + 1 >= BUFSIZ) {
2571 psbs("last_modifying_cmd overrun");
2573 // add new char to q
2574 last_modifying_cmd[len] = c;
2579 #else /* CONFIG_FEATURE_VI_DOT_CMD */
2580 c = readit(); // get the users input
2581 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
2582 return (c); // return the char, where ever it came from
2585 static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
2590 static Byte *obufp = NULL;
2592 strcpy((char *) buf, (char *) prompt);
2593 *status_buffer = '\0'; // clear the status buffer
2594 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2595 clear_to_eol(); // clear the line
2596 write(1, prompt, strlen((char *) prompt)); // write out the :, /, or ? prompt
2598 for (i = strlen((char *) buf); i < BUFSIZ;) {
2599 c = get_one_char(); // read user input
2600 if (c == '\n' || c == '\r' || c == 27)
2601 break; // is this end of input
2602 if (c == erase_char) { // user wants to erase prev char
2603 i--; // backup to prev char
2604 buf[i] = '\0'; // erase the char
2605 buf[i + 1] = '\0'; // null terminate buffer
2606 write(1, "
\b \b", 3); // erase char on screen
2607 if (i <= 0) { // user backs up before b-o-l, exit
2611 buf[i] = c; // save char in buffer
2612 buf[i + 1] = '\0'; // make sure buffer is null terminated
2613 write(1, buf + i, 1); // echo the char back to user
2620 obufp = (Byte *) xstrdup((char *) buf);
2624 static int file_size(Byte * fn) // what is the byte size of "fn"
2629 if (fn == 0 || strlen(fn) <= 0)
2632 sr = stat((char *) fn, &st_buf); // see if file exists
2634 cnt = (int) st_buf.st_size;
2639 static int file_insert(Byte * fn, Byte * p, int size)
2644 #ifdef CONFIG_FEATURE_VI_READONLY
2646 #endif /* CONFIG_FEATURE_VI_READONLY */
2647 if (fn == 0 || strlen((char*) fn) <= 0) {
2648 psbs("No filename given");
2652 // OK- this is just a no-op
2657 psbs("Trying to insert a negative number (%d) of characters", size);
2660 if (p < text || p > end) {
2661 psbs("Trying to insert file outside of memory");
2665 // see if we can open the file
2666 #ifdef CONFIG_FEATURE_VI_READONLY
2667 if (vi_readonly) goto fi1; // do not try write-mode
2669 fd = open((char *) fn, O_RDWR); // assume read & write
2671 // could not open for writing- maybe file is read only
2672 #ifdef CONFIG_FEATURE_VI_READONLY
2675 fd = open((char *) fn, O_RDONLY); // try read-only
2677 psbs("\"%s\" %s", fn, "could not open file");
2680 #ifdef CONFIG_FEATURE_VI_READONLY
2681 // got the file- read-only
2683 #endif /* CONFIG_FEATURE_VI_READONLY */
2685 p = text_hole_make(p, size);
2686 cnt = read(fd, p, size);
2690 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2691 psbs("could not read file \"%s\"", fn);
2692 } else if (cnt < size) {
2693 // There was a partial read, shrink unused space text[]
2694 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2695 psbs("could not read all of file \"%s\"", fn);
2698 file_modified = TRUE;
2703 static int file_write(Byte * fn, Byte * first, Byte * last)
2705 int fd, cnt, charcnt;
2708 psbs("No current filename");
2712 // FIXIT- use the correct umask()
2713 fd = open((char *) fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664);
2716 cnt = last - first + 1;
2717 charcnt = write(fd, first, cnt);
2718 if (charcnt == cnt) {
2720 //file_modified= FALSE; // the file has not been modified
2728 //----- Terminal Drawing ---------------------------------------
2729 // The terminal is made up of 'rows' line of 'columns' columns.
2730 // classicly this would be 24 x 80.
2731 // screen coordinates
2737 // 23,0 ... 23,79 status line
2740 //----- Move the cursor to row x col (count from 0, not 1) -------
2741 static void place_cursor(int row, int col, int opti)
2746 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2749 // char cm3[BUFSIZ];
2751 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2753 memset(cm1, '\0', BUFSIZ - 1); // clear the buffer
2755 if (row < 0) row = 0;
2756 if (row >= rows) row = rows - 1;
2757 if (col < 0) col = 0;
2758 if (col >= columns) col = columns - 1;
2760 //----- 1. Try the standard terminal ESC sequence
2761 sprintf((char *) cm1, CMrc, row + 1, col + 1);
2763 if (! opti) goto pc0;
2765 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2766 //----- find the minimum # of chars to move cursor -------------
2767 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2768 memset(cm2, '\0', BUFSIZ - 1); // clear the buffer
2770 // move to the correct row
2771 while (row < Rrow) {
2772 // the cursor has to move up
2776 while (row > Rrow) {
2777 // the cursor has to move down
2778 strcat(cm2, CMdown);
2782 // now move to the correct column
2783 strcat(cm2, "\r"); // start at col 0
2784 // just send out orignal source char to get to correct place
2785 screenp = &screen[row * columns]; // start of screen line
2786 strncat(cm2, screenp, col);
2788 //----- 3. Try some other way of moving cursor
2789 //---------------------------------------------
2791 // pick the shortest cursor motion to send out
2793 if (strlen(cm2) < strlen(cm)) {
2795 } /* else if (strlen(cm3) < strlen(cm)) {
2798 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2801 if (l) write(1, cm, l); // move the cursor
2804 //----- Erase from cursor to end of line -----------------------
2805 static void clear_to_eol()
2807 write(1, Ceol, strlen(Ceol)); // Erase from cursor to end of line
2810 //----- Erase from cursor to end of screen -----------------------
2811 static void clear_to_eos()
2813 write(1, Ceos, strlen(Ceos)); // Erase from cursor to end of screen
2816 //----- Start standout mode ------------------------------------
2817 static void standout_start() // send "start reverse video" sequence
2819 write(1, SOs, strlen(SOs)); // Start reverse video mode
2822 //----- End standout mode --------------------------------------
2823 static void standout_end() // send "end reverse video" sequence
2825 write(1, SOn, strlen(SOn)); // End reverse video mode
2828 //----- Flash the screen --------------------------------------
2829 static void flash(int h)
2831 standout_start(); // send "start reverse video" sequence
2834 standout_end(); // send "end reverse video" sequence
2840 write(1, bell, strlen(bell)); // send out a bell character
2843 static void indicate_error(char c)
2845 #ifdef CONFIG_FEATURE_VI_CRASHME
2847 return; // generate a random command
2848 #endif /* CONFIG_FEATURE_VI_CRASHME */
2849 if (err_method == 0) {
2856 //----- Screen[] Routines --------------------------------------
2857 //----- Erase the Screen[] memory ------------------------------
2858 static void screen_erase()
2860 memset(screen, ' ', screensize); // clear new screen
2863 //----- Draw the status line at bottom of the screen -------------
2864 static void show_status_line(void)
2866 static int last_cksum;
2869 cnt = strlen((char *) status_buffer);
2870 for (cksum= l= 0; l < cnt; l++) { cksum += (int)(status_buffer[l]); }
2871 // don't write the status line unless it changes
2872 if (cnt > 0 && last_cksum != cksum) {
2873 last_cksum= cksum; // remember if we have seen this line
2874 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
2875 write(1, status_buffer, cnt);
2877 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2881 //----- format the status buffer, the bottom line of screen ------
2882 // print status buffer, with STANDOUT mode
2883 static void psbs(char *format, ...)
2887 va_start(args, format);
2888 strcpy((char *) status_buffer, SOs); // Terminal standout mode on
2889 vsprintf((char *) status_buffer + strlen((char *) status_buffer), format,
2891 strcat((char *) status_buffer, SOn); // Terminal standout mode off
2897 // print status buffer
2898 static void psb(char *format, ...)
2902 va_start(args, format);
2903 vsprintf((char *) status_buffer, format, args);
2908 static void ni(Byte * s) // display messages
2912 print_literal(buf, s);
2913 psbs("\'%s\' is not implemented", buf);
2916 static void edit_status(void) // show file status on status line
2918 int cur, tot, percent;
2920 cur = count_lines(text, dot);
2921 tot = count_lines(text, end - 1);
2922 // current line percent
2923 // ------------- ~~ ----------
2926 percent = (100 * cur) / tot;
2932 #ifdef CONFIG_FEATURE_VI_READONLY
2934 #endif /* CONFIG_FEATURE_VI_READONLY */
2935 "%s line %d of %d --%d%%--",
2936 (cfn != 0 ? (char *) cfn : "No file"),
2937 #ifdef CONFIG_FEATURE_VI_READONLY
2938 ((vi_readonly || readonly) ? " [Read only]" : ""),
2939 #endif /* CONFIG_FEATURE_VI_READONLY */
2940 (file_modified ? " [modified]" : ""),
2944 //----- Force refresh of all Lines -----------------------------
2945 static void redraw(int full_screen)
2947 place_cursor(0, 0, FALSE); // put cursor in correct place
2948 clear_to_eos(); // tel terminal to erase display
2949 screen_erase(); // erase the internal screen buffer
2950 refresh(full_screen); // this will redraw the entire display
2953 //----- Format a text[] line into a buffer ---------------------
2954 static void format_line(Byte *dest, Byte *src, int li)
2959 for (co= 0; co < MAX_SCR_COLS; co++) {
2960 c= ' '; // assume blank
2961 if (li > 0 && co == 0) {
2962 c = '~'; // not first line, assume Tilde
2964 // are there chars in text[] and have we gone past the end
2965 if (text < end && src < end) {
2970 if (c < ' ' || c > '~') {
2974 for (; (co % tabstop) != (tabstop - 1); co++) {
2979 c |= '@'; // make it visible
2980 c &= 0x7f; // get rid of hi bit
2983 // the co++ is done here so that the column will
2984 // not be overwritten when we blank-out the rest of line
2991 //----- Refresh the changed screen lines -----------------------
2992 // Copy the source line from text[] into the buffer and note
2993 // if the current screenline is different from the new buffer.
2994 // If they differ then that line needs redrawing on the terminal.
2996 static void refresh(int full_screen)
2998 static int old_offset;
3000 Byte buf[MAX_SCR_COLS];
3001 Byte *tp, *sp; // pointer into text[] and screen[]
3002 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
3003 int last_li= -2; // last line that changed- for optimizing cursor movement
3004 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
3006 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
3008 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
3009 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
3010 tp = screenbegin; // index into text[] of top line
3012 // compare text[] to screen[] and mark screen[] lines that need updating
3013 for (li = 0; li < rows - 1; li++) {
3014 int cs, ce; // column start & end
3015 memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer
3016 buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer
3017 // format current text line into buf
3018 format_line(buf, tp, li);
3020 // skip to the end of the current text[] line
3021 while (tp < end && *tp++ != '\n') /*no-op*/ ;
3023 // see if there are any changes between vitual screen and buf
3024 changed = FALSE; // assume no change
3027 sp = &screen[li * columns]; // start of screen line
3029 // force re-draw of every single column from 0 - columns-1
3032 // compare newly formatted buffer with virtual screen
3033 // look forward for first difference between buf and screen
3034 for ( ; cs <= ce; cs++) {
3035 if (buf[cs + offset] != sp[cs]) {
3036 changed = TRUE; // mark for redraw
3041 // look backward for last difference between buf and screen
3042 for ( ; ce >= cs; ce--) {
3043 if (buf[ce + offset] != sp[ce]) {
3044 changed = TRUE; // mark for redraw
3048 // now, cs is index of first diff, and ce is index of last diff
3050 // if horz offset has changed, force a redraw
3051 if (offset != old_offset) {
3056 // make a sanity check of columns indexes
3058 if (ce > columns-1) ce= columns-1;
3059 if (cs > ce) { cs= 0; ce= columns-1; }
3060 // is there a change between vitual screen and buf
3062 // copy changed part of buffer to virtual screen
3063 memmove(sp+cs, buf+(cs+offset), ce-cs+1);
3065 // move cursor to column of first change
3066 if (offset != old_offset) {
3067 // opti_cur_move is still too stupid
3068 // to handle offsets correctly
3069 place_cursor(li, cs, FALSE);
3071 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
3072 // if this just the next line
3073 // try to optimize cursor movement
3074 // otherwise, use standard ESC sequence
3075 place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE);
3077 #else /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
3078 place_cursor(li, cs, FALSE); // use standard ESC sequence
3079 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
3082 // write line out to terminal
3083 write(1, sp+cs, ce-cs+1);
3084 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
3086 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
3090 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
3091 place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE);
3094 place_cursor(crow, ccol, FALSE);
3095 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
3097 if (offset != old_offset)
3098 old_offset = offset;
3101 //---------------------------------------------------------------------
3102 //----- the Ascii Chart -----------------------------------------------
3104 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3105 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3106 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
3107 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
3108 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
3109 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
3110 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
3111 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
3112 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
3113 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
3114 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
3115 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
3116 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
3117 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
3118 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
3119 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
3120 //---------------------------------------------------------------------
3122 //----- Execute a Vi Command -----------------------------------
3123 static void do_cmd(Byte c)
3125 Byte c1, *p, *q, *msg, buf[9], *save_dot;
3126 int cnt, i, j, dir, yf;
3128 c1 = c; // quiet the compiler
3129 cnt = yf = dir = 0; // quiet the compiler
3130 p = q = save_dot = msg = buf; // quiet the compiler
3131 memset(buf, '\0', 9); // clear buf
3133 /* if this is a cursor key, skip these checks */
3146 if (cmd_mode == 2) {
3147 // we are 'R'eplacing the current *dot with new char
3149 // don't Replace past E-o-l
3150 cmd_mode = 1; // convert to insert
3152 if (1 <= c && c <= 127) { // only ASCII chars
3154 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3155 dot = char_insert(dot, c); // insert new char
3160 if (cmd_mode == 1) {
3161 // hitting "Insert" twice means "R" replace mode
3162 if (c == VI_K_INSERT) goto dc5;
3163 // insert the char c at "dot"
3164 if (1 <= c && c <= 127) {
3165 dot = char_insert(dot, c); // only ASCII chars
3180 #ifdef CONFIG_FEATURE_VI_CRASHME
3181 case 0x14: // dc4 ctrl-T
3182 crashme = (crashme == 0) ? 1 : 0;
3184 #endif /* CONFIG_FEATURE_VI_CRASHME */
3213 //case 'u': // u- FIXME- there is no undo
3215 default: // unrecognised command
3224 end_cmd_q(); // stop adding to q
3225 case 0x00: // nul- ignore
3227 case 2: // ctrl-B scroll up full screen
3228 case VI_K_PAGEUP: // Cursor Key Page Up
3229 dot_scroll(rows - 2, -1);
3231 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
3232 case 0x03: // ctrl-C interrupt
3233 longjmp(restart, 1);
3235 case 26: // ctrl-Z suspend
3236 suspend_sig(SIGTSTP);
3238 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
3239 case 4: // ctrl-D scroll down half screen
3240 dot_scroll((rows - 2) / 2, 1);
3242 case 5: // ctrl-E scroll down one line
3245 case 6: // ctrl-F scroll down full screen
3246 case VI_K_PAGEDOWN: // Cursor Key Page Down
3247 dot_scroll(rows - 2, 1);
3249 case 7: // ctrl-G show current status
3252 case 'h': // h- move left
3253 case VI_K_LEFT: // cursor key Left
3254 case 8: // ctrl-H- move left (This may be ERASE char)
3255 case 127: // DEL- move left (This may be ERASE char)
3261 case 10: // Newline ^J
3262 case 'j': // j- goto next line, same col
3263 case VI_K_DOWN: // cursor key Down
3267 dot_next(); // go to next B-o-l
3268 dot = move_to_col(dot, ccol + offset); // try stay in same col
3270 case 12: // ctrl-L force redraw whole screen
3271 case 18: // ctrl-R force redraw
3272 place_cursor(0, 0, FALSE); // put cursor in correct place
3273 clear_to_eos(); // tel terminal to erase display
3275 screen_erase(); // erase the internal screen buffer
3276 refresh(TRUE); // this will redraw the entire display
3278 case 13: // Carriage Return ^M
3279 case '+': // +- goto next line
3286 case 21: // ctrl-U scroll up half screen
3287 dot_scroll((rows - 2) / 2, -1);
3289 case 25: // ctrl-Y scroll up one line
3295 cmd_mode = 0; // stop insrting
3297 *status_buffer = '\0'; // clear status buffer
3299 case ' ': // move right
3300 case 'l': // move right
3301 case VI_K_RIGHT: // Cursor Key Right
3307 #ifdef CONFIG_FEATURE_VI_YANKMARK
3308 case '"': // "- name a register to use for Delete/Yank
3309 c1 = get_one_char();
3317 case '\'': // '- goto a specific mark
3318 c1 = get_one_char();
3324 if (text <= q && q < end) {
3326 dot_begin(); // go to B-o-l
3329 } else if (c1 == '\'') { // goto previous context
3330 dot = swap_context(dot); // swap current and previous context
3331 dot_begin(); // go to B-o-l
3337 case 'm': // m- Mark a line
3338 // this is really stupid. If there are any inserts or deletes
3339 // between text[0] and dot then this mark will not point to the
3340 // correct location! It could be off by many lines!
3341 // Well..., at least its quick and dirty.
3342 c1 = get_one_char();
3346 // remember the line
3347 mark[(int) c1] = dot;
3352 case 'P': // P- Put register before
3353 case 'p': // p- put register after
3356 psbs("Nothing in register %c", what_reg());
3359 // are we putting whole lines or strings
3360 if (strchr((char *) p, '\n') != NULL) {
3362 dot_begin(); // putting lines- Put above
3365 // are we putting after very last line?
3366 if (end_line(dot) == (end - 1)) {
3367 dot = end; // force dot to end of text[]
3369 dot_next(); // next line, then put before
3374 dot_right(); // move to right, can move to NL
3376 dot = string_insert(dot, p); // insert the string
3377 end_cmd_q(); // stop adding to q
3379 case 'U': // U- Undo; replace current line with original version
3380 if (reg[Ureg] != 0) {
3381 p = begin_line(dot);
3383 p = text_hole_delete(p, q); // delete cur line
3384 p = string_insert(p, reg[Ureg]); // insert orig line
3389 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3390 case '$': // $- goto end of line
3391 case VI_K_END: // Cursor Key End
3395 dot = end_line(dot + 1);
3397 case '%': // %- find matching char of pair () [] {}
3398 for (q = dot; q < end && *q != '\n'; q++) {
3399 if (strchr("()[]{}", *q) != NULL) {
3400 // we found half of a pair
3401 p = find_pair(q, *q);
3413 case 'f': // f- forward to a user specified char
3414 last_forward_char = get_one_char(); // get the search char
3416 // dont seperate these two commands. 'f' depends on ';'
3418 //**** fall thru to ... 'i'
3419 case ';': // ;- look at rest of line for last forward char
3423 if (last_forward_char == 0) break;
3425 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3428 if (*q == last_forward_char)
3431 case '-': // -- goto prev line
3438 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3439 case '.': // .- repeat the last modifying command
3440 // Stuff the last_modifying_cmd back into stdin
3441 // and let it be re-executed.
3442 if (last_modifying_cmd != 0) {
3443 ioq = ioq_start = (Byte *) xstrdup((char *) last_modifying_cmd);
3446 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3447 #ifdef CONFIG_FEATURE_VI_SEARCH
3448 case '?': // /- search for a pattern
3449 case '/': // /- search for a pattern
3452 q = get_input_line(buf); // get input line- use "status line"
3453 if (strlen((char *) q) == 1)
3454 goto dc3; // if no pat re-use old pat
3455 if (strlen((char *) q) > 1) { // new pat- save it and find
3456 // there is a new pat
3457 if (last_search_pattern != 0) {
3458 free(last_search_pattern);
3460 last_search_pattern = (Byte *) xstrdup((char *) q);
3461 goto dc3; // now find the pattern
3463 // user changed mind and erased the "/"- do nothing
3465 case 'N': // N- backward search for last pattern
3469 dir = BACK; // assume BACKWARD search
3471 if (last_search_pattern[0] == '?') {
3475 goto dc4; // now search for pattern
3477 case 'n': // n- repeat search for last pattern
3478 // search rest of text[] starting at next char
3479 // if search fails return orignal "p" not the "p+1" address
3484 if (last_search_pattern == 0) {
3485 msg = (Byte *) "No previous regular expression";
3488 if (last_search_pattern[0] == '/') {
3489 dir = FORWARD; // assume FORWARD search
3492 if (last_search_pattern[0] == '?') {
3497 q = char_search(p, last_search_pattern + 1, dir, FULL);
3499 dot = q; // good search, update "dot"
3503 // no pattern found between "dot" and "end"- continue at top
3508 q = char_search(p, last_search_pattern + 1, dir, FULL);
3509 if (q != NULL) { // found something
3510 dot = q; // found new pattern- goto it
3511 msg = (Byte *) "search hit BOTTOM, continuing at TOP";
3513 msg = (Byte *) "search hit TOP, continuing at BOTTOM";
3516 msg = (Byte *) "Pattern not found";
3521 case '{': // {- move backward paragraph
3522 q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
3523 if (q != NULL) { // found blank line
3524 dot = next_line(q); // move to next blank line
3527 case '}': // }- move forward paragraph
3528 q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
3529 if (q != NULL) { // found blank line
3530 dot = next_line(q); // move to next blank line
3533 #endif /* CONFIG_FEATURE_VI_SEARCH */
3534 case '0': // 0- goto begining of line
3544 if (c == '0' && cmdcnt < 1) {
3545 dot_begin(); // this was a standalone zero
3547 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3550 case ':': // :- the colon mode commands
3551 p = get_input_line((Byte *) ":"); // get input line- use "status line"
3552 #ifdef CONFIG_FEATURE_VI_COLON
3553 colon(p); // execute the command
3554 #else /* CONFIG_FEATURE_VI_COLON */
3556 p++; // move past the ':'
3557 cnt = strlen((char *) p);
3560 if (strncasecmp((char *) p, "quit", cnt) == 0 ||
3561 strncasecmp((char *) p, "q!", cnt) == 0) { // delete lines
3562 if (file_modified && p[1] != '!') {
3563 psbs("No write since last change (:quit! overrides)");
3567 } else if (strncasecmp((char *) p, "write", cnt) == 0 ||
3568 strncasecmp((char *) p, "wq", cnt) == 0 ||
3569 strncasecmp((char *) p, "x", cnt) == 0) {
3570 cnt = file_write(cfn, text, end - 1);
3571 file_modified = FALSE;
3572 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
3573 if (p[0] == 'x' || p[1] == 'q') {
3576 } else if (strncasecmp((char *) p, "file", cnt) == 0 ) {
3577 edit_status(); // show current file status
3578 } else if (sscanf((char *) p, "%d", &j) > 0) {
3579 dot = find_line(j); // go to line # j
3581 } else { // unrecognised cmd
3584 #endif /* CONFIG_FEATURE_VI_COLON */
3586 case '<': // <- Left shift something
3587 case '>': // >- Right shift something
3588 cnt = count_lines(text, dot); // remember what line we are on
3589 c1 = get_one_char(); // get the type of thing to delete
3590 find_range(&p, &q, c1);
3591 (void) yank_delete(p, q, 1, YANKONLY); // save copy before change
3594 i = count_lines(p, q); // # of lines we are shifting
3595 for ( ; i > 0; i--, p = next_line(p)) {
3597 // shift left- remove tab or 8 spaces
3599 // shrink buffer 1 char
3600 (void) text_hole_delete(p, p);
3601 } else if (*p == ' ') {
3602 // we should be calculating columns, not just SPACE
3603 for (j = 0; *p == ' ' && j < tabstop; j++) {
3604 (void) text_hole_delete(p, p);
3607 } else if (c == '>') {
3608 // shift right -- add tab or 8 spaces
3609 (void) char_insert(p, '\t');
3612 dot = find_line(cnt); // what line were we on
3614 end_cmd_q(); // stop adding to q
3616 case 'A': // A- append at e-o-l
3617 dot_end(); // go to e-o-l
3618 //**** fall thru to ... 'a'
3619 case 'a': // a- append after current char
3624 case 'B': // B- back a blank-delimited Word
3625 case 'E': // E- end of a blank-delimited word
3626 case 'W': // W- forward a blank-delimited word
3633 if (c == 'W' || isspace(dot[dir])) {
3634 dot = skip_thing(dot, 1, dir, S_TO_WS);
3635 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3638 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3640 case 'C': // C- Change to e-o-l
3641 case 'D': // D- delete to e-o-l
3643 dot = dollar_line(dot); // move to before NL
3644 // copy text into a register and delete
3645 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3647 goto dc_i; // start inserting
3648 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3650 end_cmd_q(); // stop adding to q
3651 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3653 case 'G': // G- goto to a line number (default= E-O-F)
3654 dot = end - 1; // assume E-O-F
3656 dot = find_line(cmdcnt); // what line is #cmdcnt
3660 case 'H': // H- goto top line on screen
3662 if (cmdcnt > (rows - 1)) {
3663 cmdcnt = (rows - 1);
3670 case 'I': // I- insert before first non-blank
3673 //**** fall thru to ... 'i'
3674 case 'i': // i- insert before current char
3675 case VI_K_INSERT: // Cursor Key Insert
3677 cmd_mode = 1; // start insrting
3678 psb("-- Insert --");
3680 case 'J': // J- join current and next lines together
3684 dot_end(); // move to NL
3685 if (dot < end - 1) { // make sure not last char in text[]
3686 *dot++ = ' '; // replace NL with space
3687 while (isblnk(*dot)) { // delete leading WS
3691 end_cmd_q(); // stop adding to q
3693 case 'L': // L- goto bottom line on screen
3695 if (cmdcnt > (rows - 1)) {
3696 cmdcnt = (rows - 1);
3704 case 'M': // M- goto middle line on screen
3706 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3707 dot = next_line(dot);
3709 case 'O': // O- open a empty line above
3711 p = begin_line(dot);
3712 if (p[-1] == '\n') {
3714 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3716 dot = char_insert(dot, '\n');
3719 dot = char_insert(dot, '\n'); // i\n ESC
3724 case 'R': // R- continuous Replace char
3727 psb("-- Replace --");
3729 case 'X': // X- delete char before dot
3730 case 'x': // x- delete the current char
3731 case 's': // s- substitute the current char
3738 if (dot[dir] != '\n') {
3740 dot--; // delete prev char
3741 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3744 goto dc_i; // start insrting
3745 end_cmd_q(); // stop adding to q
3747 case 'Z': // Z- if modified, {write}; exit
3748 // ZZ means to save file (if necessary), then exit
3749 c1 = get_one_char();
3755 #ifdef CONFIG_FEATURE_VI_READONLY
3758 #endif /* CONFIG_FEATURE_VI_READONLY */
3760 cnt = file_write(cfn, text, end - 1);
3761 if (cnt == (end - 1 - text + 1)) {
3768 case '^': // ^- move to first non-blank on line
3772 case 'b': // b- back a word
3773 case 'e': // e- end of word
3780 if ((dot + dir) < text || (dot + dir) > end - 1)
3783 if (isspace(*dot)) {
3784 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3786 if (isalnum(*dot) || *dot == '_') {
3787 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3788 } else if (ispunct(*dot)) {
3789 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3792 case 'c': // c- change something
3793 case 'd': // d- delete something
3794 #ifdef CONFIG_FEATURE_VI_YANKMARK
3795 case 'y': // y- yank something
3796 case 'Y': // Y- Yank a line
3797 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3798 yf = YANKDEL; // assume either "c" or "d"
3799 #ifdef CONFIG_FEATURE_VI_YANKMARK
3800 if (c == 'y' || c == 'Y')
3802 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3805 c1 = get_one_char(); // get the type of thing to delete
3806 find_range(&p, &q, c1);
3807 if (c1 == 27) { // ESC- user changed mind and wants out
3808 c = c1 = 27; // Escape- do nothing
3809 } else if (strchr("wW", c1)) {
3811 // don't include trailing WS as part of word
3812 while (isblnk(*q)) {
3813 if (q <= text || q[-1] == '\n')
3818 dot = yank_delete(p, q, 0, yf); // delete word
3819 } else if (strchr("^0bBeEft$", c1)) {
3820 // single line copy text into a register and delete
3821 dot = yank_delete(p, q, 0, yf); // delete word
3822 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
3823 // multiple line copy text into a register and delete
3824 dot = yank_delete(p, q, 1, yf); // delete lines
3826 dot = char_insert(dot, '\n');
3827 // on the last line of file don't move to prev line
3828 if (dot != (end-1)) {
3831 } else if (c == 'd') {
3836 // could not recognize object
3837 c = c1 = 27; // error-
3841 // if CHANGING, not deleting, start inserting after the delete
3843 strcpy((char *) buf, "Change");
3844 goto dc_i; // start inserting
3847 strcpy((char *) buf, "Delete");
3849 #ifdef CONFIG_FEATURE_VI_YANKMARK
3850 if (c == 'y' || c == 'Y') {
3851 strcpy((char *) buf, "Yank");
3854 q = p + strlen((char *) p);
3855 for (cnt = 0; p <= q; p++) {
3859 psb("%s %d lines (%d chars) using [%c]",
3860 buf, cnt, strlen((char *) reg[YDreg]), what_reg());
3861 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3862 end_cmd_q(); // stop adding to q
3865 case 'k': // k- goto prev line, same col
3866 case VI_K_UP: // cursor key Up
3871 dot = move_to_col(dot, ccol + offset); // try stay in same col
3873 case 'r': // r- replace the current char with user input
3874 c1 = get_one_char(); // get the replacement char
3877 file_modified = TRUE; // has the file been modified
3879 end_cmd_q(); // stop adding to q
3881 case 't': // t- move to char prior to next x
3882 last_forward_char = get_one_char();
3884 if (*dot == last_forward_char)
3886 last_forward_char= 0;
3888 case 'w': // w- forward a word
3892 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3893 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3894 } else if (ispunct(*dot)) { // we are on PUNCT
3895 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3898 dot++; // move over word
3899 if (isspace(*dot)) {
3900 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3904 c1 = get_one_char(); // get the replacement char
3907 cnt = (rows - 2) / 2; // put dot at center
3909 cnt = rows - 2; // put dot at bottom
3910 screenbegin = begin_line(dot); // start dot at top
3911 dot_scroll(cnt, -1);
3913 case '|': // |- move to column "cmdcnt"
3914 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3916 case '~': // ~- flip the case of letters a-z -> A-Z
3920 if (islower(*dot)) {
3921 *dot = toupper(*dot);
3922 file_modified = TRUE; // has the file been modified
3923 } else if (isupper(*dot)) {
3924 *dot = tolower(*dot);
3925 file_modified = TRUE; // has the file been modified
3928 end_cmd_q(); // stop adding to q
3930 //----- The Cursor and Function Keys -----------------------------
3931 case VI_K_HOME: // Cursor Key Home
3934 // The Fn keys could point to do_macro which could translate them
3935 case VI_K_FUN1: // Function Key F1
3936 case VI_K_FUN2: // Function Key F2
3937 case VI_K_FUN3: // Function Key F3
3938 case VI_K_FUN4: // Function Key F4
3939 case VI_K_FUN5: // Function Key F5
3940 case VI_K_FUN6: // Function Key F6
3941 case VI_K_FUN7: // Function Key F7
3942 case VI_K_FUN8: // Function Key F8
3943 case VI_K_FUN9: // Function Key F9
3944 case VI_K_FUN10: // Function Key F10
3945 case VI_K_FUN11: // Function Key F11
3946 case VI_K_FUN12: // Function Key F12
3951 // if text[] just became empty, add back an empty line
3953 (void) char_insert(text, '\n'); // start empty buf with dummy line
3956 // it is OK for dot to exactly equal to end, otherwise check dot validity
3958 dot = bound_dot(dot); // make sure "dot" is valid
3960 #ifdef CONFIG_FEATURE_VI_YANKMARK
3961 check_context(c); // update the current context
3962 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3965 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3966 cnt = dot - begin_line(dot);
3967 // Try to stay off of the Newline
3968 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)