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.20 2001/12/20 23:12:47 kraai 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 static 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 static 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 static 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 #ifdef CONFIG_FEATURE_VI_DOT_CMD
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 //---------------------------------------------------------------------
762 //----- the Ascii Chart -----------------------------------------------
764 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
765 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
766 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
767 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
768 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
769 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
770 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
771 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
772 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
773 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
774 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
775 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
776 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
777 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
778 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
779 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
780 //---------------------------------------------------------------------
782 //----- Execute a Vi Command -----------------------------------
783 static void do_cmd(Byte c)
785 Byte c1, *p, *q, *msg, buf[9], *save_dot;
786 int cnt, i, j, dir, yf;
788 c1 = c; // quiet the compiler
789 cnt = yf = dir = 0; // quiet the compiler
790 p = q = save_dot = msg = buf; // quiet the compiler
791 memset(buf, '\0', 9); // clear buf
793 /* if this is a cursor key, skip these checks */
807 // we are 'R'eplacing the current *dot with new char
809 // don't Replace past E-o-l
810 cmd_mode = 1; // convert to insert
812 if (1 <= c && c <= 127) { // only ASCII chars
814 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
815 dot = char_insert(dot, c); // insert new char
821 // hitting "Insert" twice means "R" replace mode
822 if (c == VI_K_INSERT) goto dc5;
823 // insert the char c at "dot"
824 if (1 <= c && c <= 127) {
825 dot = char_insert(dot, c); // only ASCII chars
840 #ifdef CONFIG_FEATURE_VI_CRASHME
841 case 0x14: // dc4 ctrl-T
842 crashme = (crashme == 0) ? 1 : 0;
844 #endif /* CONFIG_FEATURE_VI_CRASHME */
873 //case 'u': // u- FIXME- there is no undo
875 default: // unrecognised command
884 end_cmd_q(); // stop adding to q
885 case 0x00: // nul- ignore
887 case 2: // ctrl-B scroll up full screen
888 case VI_K_PAGEUP: // Cursor Key Page Up
889 dot_scroll(rows - 2, -1);
891 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
892 case 0x03: // ctrl-C interrupt
895 case 26: // ctrl-Z suspend
896 suspend_sig(SIGTSTP);
898 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
899 case 4: // ctrl-D scroll down half screen
900 dot_scroll((rows - 2) / 2, 1);
902 case 5: // ctrl-E scroll down one line
905 case 6: // ctrl-F scroll down full screen
906 case VI_K_PAGEDOWN: // Cursor Key Page Down
907 dot_scroll(rows - 2, 1);
909 case 7: // ctrl-G show current status
912 case 'h': // h- move left
913 case VI_K_LEFT: // cursor key Left
914 case 8: // ctrl-H- move left (This may be ERASE char)
915 case 127: // DEL- move left (This may be ERASE char)
921 case 10: // Newline ^J
922 case 'j': // j- goto next line, same col
923 case VI_K_DOWN: // cursor key Down
927 dot_next(); // go to next B-o-l
928 dot = move_to_col(dot, ccol + offset); // try stay in same col
930 case 12: // ctrl-L force redraw whole screen
931 case 18: // ctrl-R force redraw
932 place_cursor(0, 0, FALSE); // put cursor in correct place
933 clear_to_eos(); // tel terminal to erase display
935 screen_erase(); // erase the internal screen buffer
936 refresh(TRUE); // this will redraw the entire display
938 case 13: // Carriage Return ^M
939 case '+': // +- goto next line
946 case 21: // ctrl-U scroll up half screen
947 dot_scroll((rows - 2) / 2, -1);
949 case 25: // ctrl-Y scroll up one line
955 cmd_mode = 0; // stop insrting
957 *status_buffer = '\0'; // clear status buffer
959 case ' ': // move right
960 case 'l': // move right
961 case VI_K_RIGHT: // Cursor Key Right
967 #ifdef CONFIG_FEATURE_VI_YANKMARK
968 case '"': // "- name a register to use for Delete/Yank
977 case '\'': // '- goto a specific mark
984 if (text <= q && q < end) {
986 dot_begin(); // go to B-o-l
989 } else if (c1 == '\'') { // goto previous context
990 dot = swap_context(dot); // swap current and previous context
991 dot_begin(); // go to B-o-l
997 case 'm': // m- Mark a line
998 // this is really stupid. If there are any inserts or deletes
999 // between text[0] and dot then this mark will not point to the
1000 // correct location! It could be off by many lines!
1001 // Well..., at least its quick and dirty.
1002 c1 = get_one_char();
1006 // remember the line
1007 mark[(int) c1] = dot;
1012 case 'P': // P- Put register before
1013 case 'p': // p- put register after
1016 psbs("Nothing in register %c", what_reg());
1019 // are we putting whole lines or strings
1020 if (strchr((char *) p, '\n') != NULL) {
1022 dot_begin(); // putting lines- Put above
1025 // are we putting after very last line?
1026 if (end_line(dot) == (end - 1)) {
1027 dot = end; // force dot to end of text[]
1029 dot_next(); // next line, then put before
1034 dot_right(); // move to right, can move to NL
1036 dot = string_insert(dot, p); // insert the string
1037 end_cmd_q(); // stop adding to q
1039 case 'U': // U- Undo; replace current line with original version
1040 if (reg[Ureg] != 0) {
1041 p = begin_line(dot);
1043 p = text_hole_delete(p, q); // delete cur line
1044 p = string_insert(p, reg[Ureg]); // insert orig line
1049 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1050 case '$': // $- goto end of line
1051 case VI_K_END: // Cursor Key End
1055 dot = end_line(dot + 1);
1057 case '%': // %- find matching char of pair () [] {}
1058 for (q = dot; q < end && *q != '\n'; q++) {
1059 if (strchr("()[]{}", *q) != NULL) {
1060 // we found half of a pair
1061 p = find_pair(q, *q);
1073 case 'f': // f- forward to a user specified char
1074 last_forward_char = get_one_char(); // get the search char
1076 // dont seperate these two commands. 'f' depends on ';'
1078 //**** fall thru to ... 'i'
1079 case ';': // ;- look at rest of line for last forward char
1083 if (last_forward_char == 0) break;
1085 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
1088 if (*q == last_forward_char)
1091 case '-': // -- goto prev line
1098 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1099 case '.': // .- repeat the last modifying command
1100 // Stuff the last_modifying_cmd back into stdin
1101 // and let it be re-executed.
1102 if (last_modifying_cmd != 0) {
1103 ioq = ioq_start = (Byte *) xstrdup((char *) last_modifying_cmd);
1106 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
1107 #ifdef CONFIG_FEATURE_VI_SEARCH
1108 case '?': // /- search for a pattern
1109 case '/': // /- search for a pattern
1112 q = get_input_line(buf); // get input line- use "status line"
1113 if (strlen((char *) q) == 1)
1114 goto dc3; // if no pat re-use old pat
1115 if (strlen((char *) q) > 1) { // new pat- save it and find
1116 // there is a new pat
1117 if (last_search_pattern != 0) {
1118 free(last_search_pattern);
1120 last_search_pattern = (Byte *) xstrdup((char *) q);
1121 goto dc3; // now find the pattern
1123 // user changed mind and erased the "/"- do nothing
1125 case 'N': // N- backward search for last pattern
1129 dir = BACK; // assume BACKWARD search
1131 if (last_search_pattern[0] == '?') {
1135 goto dc4; // now search for pattern
1137 case 'n': // n- repeat search for last pattern
1138 // search rest of text[] starting at next char
1139 // if search fails return orignal "p" not the "p+1" address
1144 if (last_search_pattern == 0) {
1145 msg = (Byte *) "No previous regular expression";
1148 if (last_search_pattern[0] == '/') {
1149 dir = FORWARD; // assume FORWARD search
1152 if (last_search_pattern[0] == '?') {
1157 q = char_search(p, last_search_pattern + 1, dir, FULL);
1159 dot = q; // good search, update "dot"
1163 // no pattern found between "dot" and "end"- continue at top
1168 q = char_search(p, last_search_pattern + 1, dir, FULL);
1169 if (q != NULL) { // found something
1170 dot = q; // found new pattern- goto it
1171 msg = (Byte *) "search hit BOTTOM, continuing at TOP";
1173 msg = (Byte *) "search hit TOP, continuing at BOTTOM";
1176 msg = (Byte *) "Pattern not found";
1181 case '{': // {- move backward paragraph
1182 q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
1183 if (q != NULL) { // found blank line
1184 dot = next_line(q); // move to next blank line
1187 case '}': // }- move forward paragraph
1188 q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
1189 if (q != NULL) { // found blank line
1190 dot = next_line(q); // move to next blank line
1193 #endif /* CONFIG_FEATURE_VI_SEARCH */
1194 case '0': // 0- goto begining of line
1204 if (c == '0' && cmdcnt < 1) {
1205 dot_begin(); // this was a standalone zero
1207 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
1210 case ':': // :- the colon mode commands
1211 p = get_input_line((Byte *) ":"); // get input line- use "status line"
1212 #ifdef CONFIG_FEATURE_VI_COLON
1213 colon(p); // execute the command
1214 #else /* CONFIG_FEATURE_VI_COLON */
1216 p++; // move past the ':'
1217 cnt = strlen((char *) p);
1220 if (strncasecmp((char *) p, "quit", cnt) == 0 ||
1221 strncasecmp((char *) p, "q!", cnt) == 0) { // delete lines
1222 if (file_modified && p[1] != '!') {
1223 psbs("No write since last change (:quit! overrides)");
1227 } else if (strncasecmp((char *) p, "write", cnt) == 0 ||
1228 strncasecmp((char *) p, "wq", cnt) == 0) {
1229 cnt = file_write(cfn, text, end - 1);
1230 file_modified = FALSE;
1231 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
1235 } else if (strncasecmp((char *) p, "file", cnt) == 0 ) {
1236 edit_status(); // show current file status
1237 } else if (sscanf((char *) p, "%d", &j) > 0) {
1238 dot = find_line(j); // go to line # j
1240 } else { // unrecognised cmd
1243 #endif /* CONFIG_FEATURE_VI_COLON */
1245 case '<': // <- Left shift something
1246 case '>': // >- Right shift something
1247 cnt = count_lines(text, dot); // remember what line we are on
1248 c1 = get_one_char(); // get the type of thing to delete
1249 find_range(&p, &q, c1);
1250 (void) yank_delete(p, q, 1, YANKONLY); // save copy before change
1253 i = count_lines(p, q); // # of lines we are shifting
1254 for ( ; i > 0; i--, p = next_line(p)) {
1256 // shift left- remove tab or 8 spaces
1258 // shrink buffer 1 char
1259 (void) text_hole_delete(p, p);
1260 } else if (*p == ' ') {
1261 // we should be calculating columns, not just SPACE
1262 for (j = 0; *p == ' ' && j < tabstop; j++) {
1263 (void) text_hole_delete(p, p);
1266 } else if (c == '>') {
1267 // shift right -- add tab or 8 spaces
1268 (void) char_insert(p, '\t');
1271 dot = find_line(cnt); // what line were we on
1273 end_cmd_q(); // stop adding to q
1275 case 'A': // A- append at e-o-l
1276 dot_end(); // go to e-o-l
1277 //**** fall thru to ... 'a'
1278 case 'a': // a- append after current char
1283 case 'B': // B- back a blank-delimited Word
1284 case 'E': // E- end of a blank-delimited word
1285 case 'W': // W- forward a blank-delimited word
1292 if (c == 'W' || isspace(dot[dir])) {
1293 dot = skip_thing(dot, 1, dir, S_TO_WS);
1294 dot = skip_thing(dot, 2, dir, S_OVER_WS);
1297 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
1299 case 'C': // C- Change to e-o-l
1300 case 'D': // D- delete to e-o-l
1302 dot = dollar_line(dot); // move to before NL
1303 // copy text into a register and delete
1304 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
1306 goto dc_i; // start inserting
1307 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1309 end_cmd_q(); // stop adding to q
1310 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
1312 case 'G': // G- goto to a line number (default= E-O-F)
1313 dot = end - 1; // assume E-O-F
1315 dot = find_line(cmdcnt); // what line is #cmdcnt
1319 case 'H': // H- goto top line on screen
1321 if (cmdcnt > (rows - 1)) {
1322 cmdcnt = (rows - 1);
1329 case 'I': // I- insert before first non-blank
1332 //**** fall thru to ... 'i'
1333 case 'i': // i- insert before current char
1334 case VI_K_INSERT: // Cursor Key Insert
1336 cmd_mode = 1; // start insrting
1337 psb("-- Insert --");
1339 case 'J': // J- join current and next lines together
1343 dot_end(); // move to NL
1344 if (dot < end - 1) { // make sure not last char in text[]
1345 *dot++ = ' '; // replace NL with space
1346 while (isblnk(*dot)) { // delete leading WS
1350 end_cmd_q(); // stop adding to q
1352 case 'L': // L- goto bottom line on screen
1354 if (cmdcnt > (rows - 1)) {
1355 cmdcnt = (rows - 1);
1363 case 'M': // M- goto middle line on screen
1365 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
1366 dot = next_line(dot);
1368 case 'O': // O- open a empty line above
1370 p = begin_line(dot);
1371 if (p[-1] == '\n') {
1373 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
1375 dot = char_insert(dot, '\n');
1378 dot = char_insert(dot, '\n'); // i\n ESC
1383 case 'R': // R- continuous Replace char
1386 psb("-- Replace --");
1388 case 'X': // X- delete char before dot
1389 case 'x': // x- delete the current char
1390 case 's': // s- substitute the current char
1397 if (dot[dir] != '\n') {
1399 dot--; // delete prev char
1400 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
1403 goto dc_i; // start insrting
1404 end_cmd_q(); // stop adding to q
1406 case 'Z': // Z- if modified, {write}; exit
1407 // ZZ means to save file (if necessary), then exit
1408 c1 = get_one_char();
1414 #ifdef CONFIG_FEATURE_VI_READONLY
1417 #endif /* CONFIG_FEATURE_VI_READONLY */
1419 cnt = file_write(cfn, text, end - 1);
1420 if (cnt == (end - 1 - text + 1)) {
1427 case '^': // ^- move to first non-blank on line
1431 case 'b': // b- back a word
1432 case 'e': // e- end of word
1439 if ((dot + dir) < text || (dot + dir) > end - 1)
1442 if (isspace(*dot)) {
1443 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
1445 if (isalnum(*dot) || *dot == '_') {
1446 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
1447 } else if (ispunct(*dot)) {
1448 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
1451 case 'c': // c- change something
1452 case 'd': // d- delete something
1453 #ifdef CONFIG_FEATURE_VI_YANKMARK
1454 case 'y': // y- yank something
1455 case 'Y': // Y- Yank a line
1456 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1457 yf = YANKDEL; // assume either "c" or "d"
1458 #ifdef CONFIG_FEATURE_VI_YANKMARK
1459 if (c == 'y' || c == 'Y')
1461 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1464 c1 = get_one_char(); // get the type of thing to delete
1465 find_range(&p, &q, c1);
1466 if (c1 == 27) { // ESC- user changed mind and wants out
1467 c = c1 = 27; // Escape- do nothing
1468 } else if (strchr("wW", c1)) {
1470 // don't include trailing WS as part of word
1471 while (isblnk(*q)) {
1472 if (q <= text || q[-1] == '\n')
1477 dot = yank_delete(p, q, 0, yf); // delete word
1478 } else if (strchr("^0bBeEft$", c1)) {
1479 // single line copy text into a register and delete
1480 dot = yank_delete(p, q, 0, yf); // delete word
1481 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
1482 // multiple line copy text into a register and delete
1483 dot = yank_delete(p, q, 1, yf); // delete lines
1485 dot = char_insert(dot, '\n');
1486 // on the last line of file don't move to prev line
1487 if (dot != (end-1)) {
1490 } else if (c == 'd') {
1495 // could not recognize object
1496 c = c1 = 27; // error-
1500 // if CHANGING, not deleting, start inserting after the delete
1502 strcpy((char *) buf, "Change");
1503 goto dc_i; // start inserting
1506 strcpy((char *) buf, "Delete");
1508 #ifdef CONFIG_FEATURE_VI_YANKMARK
1509 if (c == 'y' || c == 'Y') {
1510 strcpy((char *) buf, "Yank");
1513 q = p + strlen((char *) p);
1514 for (cnt = 0; p <= q; p++) {
1518 psb("%s %d lines (%d chars) using [%c]",
1519 buf, cnt, strlen((char *) reg[YDreg]), what_reg());
1520 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1521 end_cmd_q(); // stop adding to q
1524 case 'k': // k- goto prev line, same col
1525 case VI_K_UP: // cursor key Up
1530 dot = move_to_col(dot, ccol + offset); // try stay in same col
1532 case 'r': // r- replace the current char with user input
1533 c1 = get_one_char(); // get the replacement char
1536 file_modified = TRUE; // has the file been modified
1538 end_cmd_q(); // stop adding to q
1540 case 't': // t- move to char prior to next x
1541 last_forward_char = get_one_char();
1543 if (*dot == last_forward_char)
1545 last_forward_char= 0;
1547 case 'w': // w- forward a word
1551 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
1552 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
1553 } else if (ispunct(*dot)) { // we are on PUNCT
1554 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
1557 dot++; // move over word
1558 if (isspace(*dot)) {
1559 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
1563 c1 = get_one_char(); // get the replacement char
1566 cnt = (rows - 2) / 2; // put dot at center
1568 cnt = rows - 2; // put dot at bottom
1569 screenbegin = begin_line(dot); // start dot at top
1570 dot_scroll(cnt, -1);
1572 case '|': // |- move to column "cmdcnt"
1573 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
1575 case '~': // ~- flip the case of letters a-z -> A-Z
1579 if (islower(*dot)) {
1580 *dot = toupper(*dot);
1581 file_modified = TRUE; // has the file been modified
1582 } else if (isupper(*dot)) {
1583 *dot = tolower(*dot);
1584 file_modified = TRUE; // has the file been modified
1587 end_cmd_q(); // stop adding to q
1589 //----- The Cursor and Function Keys -----------------------------
1590 case VI_K_HOME: // Cursor Key Home
1593 // The Fn keys could point to do_macro which could translate them
1594 case VI_K_FUN1: // Function Key F1
1595 case VI_K_FUN2: // Function Key F2
1596 case VI_K_FUN3: // Function Key F3
1597 case VI_K_FUN4: // Function Key F4
1598 case VI_K_FUN5: // Function Key F5
1599 case VI_K_FUN6: // Function Key F6
1600 case VI_K_FUN7: // Function Key F7
1601 case VI_K_FUN8: // Function Key F8
1602 case VI_K_FUN9: // Function Key F9
1603 case VI_K_FUN10: // Function Key F10
1604 case VI_K_FUN11: // Function Key F11
1605 case VI_K_FUN12: // Function Key F12
1610 // if text[] just became empty, add back an empty line
1612 (void) char_insert(text, '\n'); // start empty buf with dummy line
1615 // it is OK for dot to exactly equal to end, otherwise check dot validity
1617 dot = bound_dot(dot); // make sure "dot" is valid
1619 #ifdef CONFIG_FEATURE_VI_YANKMARK
1620 check_context(c); // update the current context
1621 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1624 cmdcnt = 0; // cmd was not a number, reset cmdcnt
1625 cnt = dot - begin_line(dot);
1626 // Try to stay off of the Newline
1627 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
1631 //----- The Colon commands -------------------------------------
1632 #ifdef CONFIG_FEATURE_VI_COLON
1633 static Byte *get_one_address(Byte * p, int *addr) // get colon addr, if present
1638 #ifdef CONFIG_FEATURE_VI_YANKMARK
1640 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1641 #ifdef CONFIG_FEATURE_VI_SEARCH
1642 Byte *pat, buf[BUFSIZ];
1643 #endif /* CONFIG_FEATURE_VI_SEARCH */
1645 *addr = -1; // assume no addr
1646 if (*p == '.') { // the current line
1648 q = begin_line(dot);
1649 *addr = count_lines(text, q);
1650 #ifdef CONFIG_FEATURE_VI_YANKMARK
1651 } else if (*p == '\'') { // is this a mark addr
1655 if (c >= 'a' && c <= 'z') {
1659 if (q != NULL) { // is mark valid
1660 *addr = count_lines(text, q); // count lines
1663 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1664 #ifdef CONFIG_FEATURE_VI_SEARCH
1665 } else if (*p == '/') { // a search pattern
1667 for (p++; *p; p++) {
1673 pat = (Byte *) xstrdup((char *) buf); // save copy of pattern
1676 q = char_search(dot, pat, FORWARD, FULL);
1678 *addr = count_lines(text, q);
1681 #endif /* CONFIG_FEATURE_VI_SEARCH */
1682 } else if (*p == '$') { // the last line in file
1684 q = begin_line(end - 1);
1685 *addr = count_lines(text, q);
1686 } else if (isdigit(*p)) { // specific line number
1687 sscanf((char *) p, "%d%n", addr, &st);
1689 } else { // I don't reconise this
1690 // unrecognised address- assume -1
1696 static Byte *get_address(Byte *p, int *b, int *e) // get two colon addrs, if present
1698 //----- get the address' i.e., 1,3 'a,'b -----
1699 // get FIRST addr, if present
1701 p++; // skip over leading spaces
1702 if (*p == '%') { // alias for 1,$
1705 *e = count_lines(text, end-1);
1708 p = get_one_address(p, b);
1711 if (*p == ',') { // is there a address seperator
1715 // get SECOND addr, if present
1716 p = get_one_address(p, e);
1720 p++; // skip over trailing spaces
1724 static void colon(Byte * buf)
1726 Byte c, *orig_buf, *buf1, *q, *r;
1727 Byte *fn, cmd[BUFSIZ], args[BUFSIZ];
1728 int i, l, li, ch, st, b, e;
1729 int useforce, forced;
1732 // :3154 // if (-e line 3154) goto it else stay put
1733 // :4,33w! foo // write a portion of buffer to file "foo"
1734 // :w // write all of buffer to current file
1736 // :q! // quit- dont care about modified file
1737 // :'a,'z!sort -u // filter block through sort
1738 // :'f // goto mark "f"
1739 // :'fl // list literal the mark "f" line
1740 // :.r bar // read file "bar" into buffer before dot
1741 // :/123/,/abc/d // delete lines from "123" line to "abc" line
1742 // :/xyz/ // goto the "xyz" line
1743 // :s/find/replace/ // substitute pattern "find" with "replace"
1744 // :!<cmd> // run <cmd> then return
1746 if (strlen((char *) buf) <= 0)
1749 buf++; // move past the ':'
1751 forced = useforce = FALSE;
1752 li = st = ch = i = 0;
1754 q = text; // assume 1,$ for the range
1756 li = count_lines(text, end - 1);
1757 fn = cfn; // default to current file
1758 memset(cmd, '\0', BUFSIZ); // clear cmd[]
1759 memset(args, '\0', BUFSIZ); // clear args[]
1761 // look for optional address(es) :. :1 :1,9 :'q,'a :%
1762 buf = get_address(buf, &b, &e);
1764 // remember orig command line
1767 // get the COMMAND into cmd[]
1769 while (*buf != '\0') {
1774 // get any ARGuments
1775 while (isblnk(*buf))
1777 strcpy((char *) args, (char *) buf);
1778 buf1 = last_char_is((char *)cmd, '!');
1781 *buf1 = '\0'; // get rid of !
1784 // if there is only one addr, then the addr
1785 // is the line number of the single line the
1786 // user wants. So, reset the end
1787 // pointer to point at end of the "b" line
1788 q = find_line(b); // what line is #b
1793 // we were given two addrs. change the
1794 // end pointer to the addr given by user.
1795 r = find_line(e); // what line is #e
1799 // ------------ now look for the command ------------
1800 i = strlen((char *) cmd);
1801 if (i == 0) { // :123CR goto line #123
1803 dot = find_line(b); // what line is #b
1806 } else if (strncmp((char *) cmd, "!", 1) == 0) { // run a cmd
1807 // :!ls run the <cmd>
1808 (void) alarm(0); // wait for input- no alarms
1809 place_cursor(rows - 1, 0, FALSE); // go to Status line
1810 clear_to_eol(); // clear the line
1812 system(orig_buf+1); // run the cmd
1814 Hit_Return(); // let user see results
1815 (void) alarm(3); // done waiting for input
1816 } else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address
1817 if (b < 0) { // no addr given- use defaults
1818 b = e = count_lines(text, dot);
1821 } else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines
1822 if (b < 0) { // no addr given- use defaults
1823 q = begin_line(dot); // assume .,. for the range
1826 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
1828 } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file
1831 // don't edit, if the current file has been modified
1832 if (file_modified && ! useforce) {
1833 psbs("No write since last change (:edit! overrides)");
1836 if (strlen(args) > 0) {
1837 // the user supplied a file name
1839 } else if (cfn != 0 && strlen(cfn) > 0) {
1840 // no user supplied name- use the current filename
1844 // no user file name, no current name- punt
1845 psbs("No current filename");
1849 // see if file exists- if not, its just a new file request
1850 if ((sr=stat((char*)fn, &st_buf)) < 0) {
1851 // This is just a request for a new file creation.
1852 // The file_insert below will fail but we get
1853 // an empty buffer with a file name. Then the "write"
1854 // command can do the create.
1856 if ((st_buf.st_mode & (S_IFREG)) == 0) {
1857 // This is not a regular file
1858 psbs("\"%s\" is not a regular file", fn);
1861 if ((st_buf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
1862 // dont have any read permissions
1863 psbs("\"%s\" is not readable", fn);
1868 // There is a read-able regular file
1869 // make this the current file
1870 q = (Byte *) xstrdup((char *) fn); // save the cfn
1872 free(cfn); // free the old name
1873 cfn = q; // remember new cfn
1876 // delete all the contents of text[]
1877 new_text(2 * file_size(fn));
1878 screenbegin = dot = end = text;
1881 ch = file_insert(fn, text, file_size(fn));
1884 // start empty buf with dummy line
1885 (void) char_insert(text, '\n');
1888 file_modified = FALSE;
1889 #ifdef CONFIG_FEATURE_VI_YANKMARK
1890 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) {
1891 free(reg[Ureg]); // free orig line reg- for 'U'
1894 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) {
1895 free(reg[YDreg]); // free default yank/delete register
1898 for (li = 0; li < 28; li++) {
1901 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1902 // how many lines in text[]?
1903 li = count_lines(text, end - 1);
1905 #ifdef CONFIG_FEATURE_VI_READONLY
1907 #endif /* CONFIG_FEATURE_VI_READONLY */
1909 (sr < 0 ? " [New file]" : ""),
1910 #ifdef CONFIG_FEATURE_VI_READONLY
1911 ((vi_readonly || readonly) ? " [Read only]" : ""),
1912 #endif /* CONFIG_FEATURE_VI_READONLY */
1914 } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this
1915 if (b != -1 || e != -1) {
1916 ni((Byte *) "No address allowed on this command");
1919 if (strlen((char *) args) > 0) {
1920 // user wants a new filename
1923 cfn = (Byte *) xstrdup((char *) args);
1925 // user wants file status info
1928 } else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available
1929 // print out values of all features
1930 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
1931 clear_to_eol(); // clear the line
1936 } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line
1937 if (b < 0) { // no addr given- use defaults
1938 q = begin_line(dot); // assume .,. for the range
1941 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
1942 clear_to_eol(); // clear the line
1943 write(1, "\r\n", 2);
1944 for (; q <= r; q++) {
1950 } else if (*q < ' ') {
1958 #ifdef CONFIG_FEATURE_VI_SET
1960 #endif /* CONFIG_FEATURE_VI_SET */
1962 } else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit
1963 (strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file
1965 // force end of argv list
1972 // don't exit if the file been modified
1973 if (file_modified) {
1974 psbs("No write since last change (:%s! overrides)",
1975 (*cmd == 'q' ? "quit" : "next"));
1978 // are there other file to edit
1979 if (*cmd == 'q' && optind < save_argc - 1) {
1980 psbs("%d more file to edit", (save_argc - optind - 1));
1983 if (*cmd == 'n' && optind >= save_argc - 1) {
1984 psbs("No more files to edit");
1988 } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[]
1990 if (strlen((char *) fn) <= 0) {
1991 psbs("No filename given");
1994 if (b < 0) { // no addr given- use defaults
1995 q = begin_line(dot); // assume "dot"
1997 // read after current line- unless user said ":0r foo"
2000 #ifdef CONFIG_FEATURE_VI_READONLY
2001 l= readonly; // remember current files' status
2003 ch = file_insert(fn, q, file_size(fn));
2004 #ifdef CONFIG_FEATURE_VI_READONLY
2008 goto vc1; // nothing was inserted
2009 // how many lines in text[]?
2010 li = count_lines(q, q + ch - 1);
2012 #ifdef CONFIG_FEATURE_VI_READONLY
2014 #endif /* CONFIG_FEATURE_VI_READONLY */
2016 #ifdef CONFIG_FEATURE_VI_READONLY
2017 ((vi_readonly || readonly) ? " [Read only]" : ""),
2018 #endif /* CONFIG_FEATURE_VI_READONLY */
2021 // if the insert is before "dot" then we need to update
2024 file_modified = TRUE;
2026 } else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args
2027 if (file_modified && ! useforce) {
2028 psbs("No write since last change (:rewind! overrides)");
2030 // reset the filenames to edit
2031 optind = fn_start - 1;
2034 #ifdef CONFIG_FEATURE_VI_SET
2035 } else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features
2036 i = 0; // offset into args
2037 if (strlen((char *) args) == 0) {
2038 // print out values of all options
2039 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2040 clear_to_eol(); // clear the line
2041 printf("----------------------------------------\r\n");
2042 #ifdef CONFIG_FEATURE_VI_SETOPTS
2045 printf("autoindent ");
2051 printf("ignorecase ");
2054 printf("showmatch ");
2055 printf("tabstop=%d ", tabstop);
2056 #endif /* CONFIG_FEATURE_VI_SETOPTS */
2060 if (strncasecmp((char *) args, "no", 2) == 0)
2061 i = 2; // ":set noautoindent"
2062 #ifdef CONFIG_FEATURE_VI_SETOPTS
2063 if (strncasecmp((char *) args + i, "autoindent", 10) == 0 ||
2064 strncasecmp((char *) args + i, "ai", 2) == 0) {
2065 autoindent = (i == 2) ? 0 : 1;
2067 if (strncasecmp((char *) args + i, "flash", 5) == 0 ||
2068 strncasecmp((char *) args + i, "fl", 2) == 0) {
2069 err_method = (i == 2) ? 0 : 1;
2071 if (strncasecmp((char *) args + i, "ignorecase", 10) == 0 ||
2072 strncasecmp((char *) args + i, "ic", 2) == 0) {
2073 ignorecase = (i == 2) ? 0 : 1;
2075 if (strncasecmp((char *) args + i, "showmatch", 9) == 0 ||
2076 strncasecmp((char *) args + i, "sm", 2) == 0) {
2077 showmatch = (i == 2) ? 0 : 1;
2079 if (strncasecmp((char *) args + i, "tabstop", 7) == 0) {
2080 sscanf(strchr((char *) args + i, '='), "=%d", &ch);
2081 if (ch > 0 && ch < columns - 1)
2084 #endif /* CONFIG_FEATURE_VI_SETOPTS */
2085 #endif /* CONFIG_FEATURE_VI_SET */
2086 #ifdef CONFIG_FEATURE_VI_SEARCH
2087 } else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
2091 // F points to the "find" pattern
2092 // R points to the "replace" pattern
2093 // replace the cmd line delimiters "/" with NULLs
2094 gflag = 0; // global replace flag
2095 c = orig_buf[1]; // what is the delimiter
2096 F = orig_buf + 2; // start of "find"
2097 R = (Byte *) strchr((char *) F, c); // middle delimiter
2098 if (!R) goto colon_s_fail;
2099 *R++ = '\0'; // terminate "find"
2100 buf1 = (Byte *) strchr((char *) R, c);
2101 if (!buf1) goto colon_s_fail;
2102 *buf1++ = '\0'; // terminate "replace"
2103 if (*buf1 == 'g') { // :s/foo/bar/g
2105 gflag++; // turn on gflag
2108 if (b < 0) { // maybe :s/foo/bar/
2109 q = begin_line(dot); // start with cur line
2110 b = count_lines(text, q); // cur line number
2113 e = b; // maybe :.s/foo/bar/
2114 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
2115 ls = q; // orig line start
2117 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
2119 // we found the "find" pattern- delete it
2120 (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1);
2121 // inset the "replace" patern
2122 (void) string_insert(buf1, R); // insert the string
2123 // check for "global" :s/foo/bar/g
2125 if ((buf1 + strlen((char *) R)) < end_line(ls)) {
2126 q = buf1 + strlen((char *) R);
2127 goto vc4; // don't let q move past cur line
2133 #endif /* CONFIG_FEATURE_VI_SEARCH */
2134 } else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version
2135 psb("%s", vi_Version);
2136 } else if ((strncasecmp((char *) cmd, "write", i) == 0) || // write text to file
2137 (strncasecmp((char *) cmd, "wq", i) == 0)) { // write text to file
2138 // is there a file name to write to?
2139 if (strlen((char *) args) > 0) {
2142 #ifdef CONFIG_FEATURE_VI_READONLY
2143 if ((vi_readonly || readonly) && ! useforce) {
2144 psbs("\"%s\" File is read only", fn);
2147 #endif /* CONFIG_FEATURE_VI_READONLY */
2148 // how many lines in text[]?
2149 li = count_lines(q, r);
2151 // see if file exists- if not, its just a new file request
2153 // if "fn" is not write-able, chmod u+w
2154 // sprintf(syscmd, "chmod u+w %s", fn);
2158 l = file_write(fn, q, r);
2159 if (useforce && forced) {
2161 // sprintf(syscmd, "chmod u-w %s", fn);
2165 psb("\"%s\" %dL, %dC", fn, li, l);
2166 if (q == text && r == end - 1 && l == ch)
2167 file_modified = FALSE;
2168 if (cmd[1] == 'q' && l == ch) {
2171 #ifdef CONFIG_FEATURE_VI_READONLY
2173 #endif /* CONFIG_FEATURE_VI_READONLY */
2174 #ifdef CONFIG_FEATURE_VI_YANKMARK
2175 } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines
2176 if (b < 0) { // no addr given- use defaults
2177 q = begin_line(dot); // assume .,. for the range
2180 text_yank(q, r, YDreg);
2181 li = count_lines(q, r);
2182 psb("Yank %d lines (%d chars) into [%c]",
2183 li, strlen((char *) reg[YDreg]), what_reg());
2184 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2190 dot = bound_dot(dot); // make sure "dot" is valid
2192 #ifdef CONFIG_FEATURE_VI_SEARCH
2194 psb(":s expression missing delimiters");
2200 static void Hit_Return(void)
2204 standout_start(); // start reverse video
2205 write(1, "[Hit return to continue]", 24);
2206 standout_end(); // end reverse video
2207 while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */
2209 redraw(TRUE); // force redraw all
2211 #endif /* CONFIG_FEATURE_VI_COLON */
2213 //----- Synchronize the cursor to Dot --------------------------
2214 static void sync_cursor(Byte * d, int *row, int *col)
2216 Byte *beg_cur, *end_cur; // begin and end of "d" line
2217 Byte *beg_scr, *end_scr; // begin and end of screen
2221 beg_cur = begin_line(d); // first char of cur line
2222 end_cur = end_line(d); // last char of cur line
2224 beg_scr = end_scr = screenbegin; // first char of screen
2225 end_scr = end_screen(); // last char of screen
2227 if (beg_cur < screenbegin) {
2228 // "d" is before top line on screen
2229 // how many lines do we have to move
2230 cnt = count_lines(beg_cur, screenbegin);
2232 screenbegin = beg_cur;
2233 if (cnt > (rows - 1) / 2) {
2234 // we moved too many lines. put "dot" in middle of screen
2235 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
2236 screenbegin = prev_line(screenbegin);
2239 } else if (beg_cur > end_scr) {
2240 // "d" is after bottom line on screen
2241 // how many lines do we have to move
2242 cnt = count_lines(end_scr, beg_cur);
2243 if (cnt > (rows - 1) / 2)
2244 goto sc1; // too many lines
2245 for (ro = 0; ro < cnt - 1; ro++) {
2246 // move screen begin the same amount
2247 screenbegin = next_line(screenbegin);
2248 // now, move the end of screen
2249 end_scr = next_line(end_scr);
2250 end_scr = end_line(end_scr);
2253 // "d" is on screen- find out which row
2255 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
2261 // find out what col "d" is on
2263 do { // drive "co" to correct column
2264 if (*tp == '\n' || *tp == '\0')
2268 co += ((tabstop - 1) - (co % tabstop));
2269 } else if (*tp < ' ') {
2270 co++; // display as ^X, use 2 columns
2272 } while (tp++ < d && ++co);
2274 // "co" is the column where "dot" is.
2275 // The screen has "columns" columns.
2276 // The currently displayed columns are 0+offset -- columns+ofset
2277 // |-------------------------------------------------------------|
2279 // offset | |------- columns ----------------|
2281 // If "co" is already in this range then we do not have to adjust offset
2282 // but, we do have to subtract the "offset" bias from "co".
2283 // If "co" is outside this range then we have to change "offset".
2284 // If the first char of a line is a tab the cursor will try to stay
2285 // in column 7, but we have to set offset to 0.
2287 if (co < 0 + offset) {
2290 if (co >= columns + offset) {
2291 offset = co - columns + 1;
2293 // if the first char of the line is a tab, and "dot" is sitting on it
2294 // force offset to 0.
2295 if (d == beg_cur && *d == '\t') {
2304 //----- Text Movement Routines ---------------------------------
2305 static Byte *begin_line(Byte * p) // return pointer to first char cur line
2307 while (p > text && p[-1] != '\n')
2308 p--; // go to cur line B-o-l
2312 static Byte *end_line(Byte * p) // return pointer to NL of cur line line
2314 while (p < end - 1 && *p != '\n')
2315 p++; // go to cur line E-o-l
2319 static Byte *dollar_line(Byte * p) // return pointer to just before NL line
2321 while (p < end - 1 && *p != '\n')
2322 p++; // go to cur line E-o-l
2323 // Try to stay off of the Newline
2324 if (*p == '\n' && (p - begin_line(p)) > 0)
2329 static Byte *prev_line(Byte * p) // return pointer first char prev line
2331 p = begin_line(p); // goto begining of cur line
2332 if (p[-1] == '\n' && p > text)
2333 p--; // step to prev line
2334 p = begin_line(p); // goto begining of prev line
2338 static Byte *next_line(Byte * p) // return pointer first char next line
2341 if (*p == '\n' && p < end - 1)
2342 p++; // step to next line
2346 //----- Text Information Routines ------------------------------
2347 static Byte *end_screen(void)
2352 // find new bottom line
2354 for (cnt = 0; cnt < rows - 2; cnt++)
2360 static int count_lines(Byte * start, Byte * stop) // count line from start to stop
2365 if (stop < start) { // start and stop are backwards- reverse them
2371 stop = end_line(stop); // get to end of this line
2372 for (q = start; q <= stop && q <= end - 1; q++) {
2379 static Byte *find_line(int li) // find begining of line #li
2383 for (q = text; li > 1; li--) {
2389 //----- Dot Movement Routines ----------------------------------
2390 static void dot_left(void)
2392 if (dot > text && dot[-1] != '\n')
2396 static void dot_right(void)
2398 if (dot < end - 1 && *dot != '\n')
2402 static void dot_begin(void)
2404 dot = begin_line(dot); // return pointer to first char cur line
2407 static void dot_end(void)
2409 dot = end_line(dot); // return pointer to last char cur line
2412 static Byte *move_to_col(Byte * p, int l)
2419 if (*p == '\n' || *p == '\0')
2423 co += ((tabstop - 1) - (co % tabstop));
2424 } else if (*p < ' ') {
2425 co++; // display as ^X, use 2 columns
2427 } while (++co <= l && p++ < end);
2431 static void dot_next(void)
2433 dot = next_line(dot);
2436 static void dot_prev(void)
2438 dot = prev_line(dot);
2441 static void dot_scroll(int cnt, int dir)
2445 for (; cnt > 0; cnt--) {
2448 // ctrl-Y scroll up one line
2449 screenbegin = prev_line(screenbegin);
2452 // ctrl-E scroll down one line
2453 screenbegin = next_line(screenbegin);
2456 // make sure "dot" stays on the screen so we dont scroll off
2457 if (dot < screenbegin)
2459 q = end_screen(); // find new bottom line
2461 dot = begin_line(q); // is dot is below bottom line?
2465 static void dot_skip_over_ws(void)
2468 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
2472 static void dot_delete(void) // delete the char at 'dot'
2474 (void) text_hole_delete(dot, dot);
2477 static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end"
2479 if (p >= end && end > text) {
2481 indicate_error('1');
2485 indicate_error('2');
2490 //----- Helper Utility Routines --------------------------------
2492 //----------------------------------------------------------------
2493 //----- Char Routines --------------------------------------------
2494 /* Chars that are part of a word-
2495 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
2496 * Chars that are Not part of a word (stoppers)
2497 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
2498 * Chars that are WhiteSpace
2499 * TAB NEWLINE VT FF RETURN SPACE
2500 * DO NOT COUNT NEWLINE AS WHITESPACE
2503 static Byte *new_screen(int ro, int co)
2509 screensize = ro * co + 8;
2510 screen = (Byte *) xmalloc(screensize);
2511 // initialize the new screen. assume this will be a empty file.
2513 // non-existant text[] lines start with a tilde (~).
2514 for (li = 1; li < ro - 1; li++) {
2515 screen[(li * co) + 0] = '~';
2520 static Byte *new_text(int size)
2523 size = 10240; // have a minimum size for new files
2528 text = (Byte *) xmalloc(size + 8);
2529 memset(text, '\0', size); // clear new text[]
2530 //text += 4; // leave some room for "oops"
2531 textend = text + size - 1;
2532 //textend -= 4; // leave some root for "oops"
2536 #ifdef CONFIG_FEATURE_VI_SEARCH
2537 static int mycmp(Byte * s1, Byte * s2, int len)
2541 i = strncmp((char *) s1, (char *) s2, len);
2542 #ifdef CONFIG_FEATURE_VI_SETOPTS
2544 i = strncasecmp((char *) s1, (char *) s2, len);
2546 #endif /* CONFIG_FEATURE_VI_SETOPTS */
2550 static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p
2552 #ifndef REGEX_SEARCH
2556 len = strlen((char *) pat);
2557 if (dir == FORWARD) {
2558 stop = end - 1; // assume range is p - end-1
2559 if (range == LIMITED)
2560 stop = next_line(p); // range is to next line
2561 for (start = p; start < stop; start++) {
2562 if (mycmp(start, pat, len) == 0) {
2566 } else if (dir == BACK) {
2567 stop = text; // assume range is text - p
2568 if (range == LIMITED)
2569 stop = prev_line(p); // range is to prev line
2570 for (start = p - len; start >= stop; start--) {
2571 if (mycmp(start, pat, len) == 0) {
2576 // pattern not found
2578 #else /*REGEX_SEARCH */
2580 struct re_pattern_buffer preg;
2584 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
2590 // assume a LIMITED forward search
2598 // count the number of chars to search over, forward or backward
2602 // RANGE could be negative if we are searching backwards
2605 q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
2607 // The pattern was not compiled
2608 psbs("bad search pattern: \"%s\": %s", pat, q);
2609 i = 0; // return p if pattern not compiled
2619 // search for the compiled pattern, preg, in p[]
2620 // range < 0- search backward
2621 // range > 0- search forward
2623 // re_search() < 0 not found or error
2624 // re_search() > 0 index of found pattern
2625 // struct pattern char int int int struct reg
2626 // re_search (*pattern_buffer, *string, size, start, range, *regs)
2627 i = re_search(&preg, q, size, 0, range, 0);
2630 i = 0; // return NULL if pattern not found
2633 if (dir == FORWARD) {
2639 #endif /*REGEX_SEARCH */
2641 #endif /* CONFIG_FEATURE_VI_SEARCH */
2643 static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
2645 if (c == 22) { // Is this an ctrl-V?
2646 p = stupid_insert(p, '^'); // use ^ to indicate literal next
2647 p--; // backup onto ^
2648 refresh(FALSE); // show the ^
2652 file_modified = TRUE; // has the file been modified
2653 } else if (c == 27) { // Is this an ESC?
2656 end_cmd_q(); // stop adding to q
2657 strcpy((char *) status_buffer, " "); // clear the status buffer
2658 if ((p[-1] != '\n') && (dot>text)) {
2661 } else if (c == erase_char) { // Is this a BS
2663 if ((p[-1] != '\n') && (dot>text)) {
2665 p = text_hole_delete(p, p); // shrink buffer 1 char
2666 #ifdef CONFIG_FEATURE_VI_DOT_CMD
2667 // also rmove char from last_modifying_cmd
2668 if (strlen((char *) last_modifying_cmd) > 0) {
2671 q = last_modifying_cmd;
2672 q[strlen((char *) q) - 1] = '\0'; // erase BS
2673 q[strlen((char *) q) - 1] = '\0'; // erase prev char
2675 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
2678 // insert a char into text[]
2679 Byte *sp; // "save p"
2682 c = '\n'; // translate \r to \n
2683 sp = p; // remember addr of insert
2684 p = stupid_insert(p, c); // insert the char
2685 #ifdef CONFIG_FEATURE_VI_SETOPTS
2686 if (showmatch && strchr(")]}", *sp) != NULL) {
2689 if (autoindent && c == '\n') { // auto indent the new line
2692 q = prev_line(p); // use prev line as templet
2693 for (; isblnk(*q); q++) {
2694 p = stupid_insert(p, *q); // insert the char
2697 #endif /* CONFIG_FEATURE_VI_SETOPTS */
2702 static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
2704 p = text_hole_make(p, 1);
2707 file_modified = TRUE; // has the file been modified
2713 static Byte find_range(Byte ** start, Byte ** stop, Byte c)
2715 Byte *save_dot, *p, *q;
2721 if (strchr("cdy><", c)) {
2722 // these cmds operate on whole lines
2723 p = q = begin_line(p);
2724 for (cnt = 1; cnt < cmdcnt; cnt++) {
2728 } else if (strchr("^%$0bBeEft", c)) {
2729 // These cmds operate on char positions
2730 do_cmd(c); // execute movement cmd
2732 } else if (strchr("wW", c)) {
2733 do_cmd(c); // execute movement cmd
2735 dot--; // move back off of next word
2736 if (dot > text && *dot == '\n')
2737 dot--; // stay off NL
2739 } else if (strchr("H-k{", c)) {
2740 // these operate on multi-lines backwards
2741 q = end_line(dot); // find NL
2742 do_cmd(c); // execute movement cmd
2745 } else if (strchr("L+j}\r\n", c)) {
2746 // these operate on multi-lines forwards
2747 p = begin_line(dot);
2748 do_cmd(c); // execute movement cmd
2749 dot_end(); // find NL
2752 c = 27; // error- return an ESC char
2765 static int st_test(Byte * p, int type, int dir, Byte * tested)
2775 if (type == S_BEFORE_WS) {
2777 test = ((!isspace(c)) || c == '\n');
2779 if (type == S_TO_WS) {
2781 test = ((!isspace(c)) || c == '\n');
2783 if (type == S_OVER_WS) {
2785 test = ((isspace(c)));
2787 if (type == S_END_PUNCT) {
2789 test = ((ispunct(c)));
2791 if (type == S_END_ALNUM) {
2793 test = ((isalnum(c)) || c == '_');
2799 static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
2803 while (st_test(p, type, dir, &c)) {
2804 // make sure we limit search to correct number of lines
2805 if (c == '\n' && --linecnt < 1)
2807 if (dir >= 0 && p >= end - 1)
2809 if (dir < 0 && p <= text)
2811 p += dir; // move to next char
2816 // find matching char of pair () [] {}
2817 static Byte *find_pair(Byte * p, Byte c)
2824 dir = 1; // assume forward
2848 for (q = p + dir; text <= q && q < end; q += dir) {
2849 // look for match, count levels of pairs (( ))
2851 level++; // increase pair levels
2853 level--; // reduce pair level
2855 break; // found matching pair
2858 q = NULL; // indicate no match
2862 #ifdef CONFIG_FEATURE_VI_SETOPTS
2863 // show the matching char of a pair, () [] {}
2864 static void showmatching(Byte * p)
2868 // we found half of a pair
2869 q = find_pair(p, *p); // get loc of matching char
2871 indicate_error('3'); // no matching char
2873 // "q" now points to matching pair
2874 save_dot = dot; // remember where we are
2875 dot = q; // go to new loc
2876 refresh(FALSE); // let the user see it
2877 (void) mysleep(40); // give user some time
2878 dot = save_dot; // go back to old loc
2882 #endif /* CONFIG_FEATURE_VI_SETOPTS */
2884 // open a hole in text[]
2885 static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
2894 cnt = end - src; // the rest of buffer
2895 if (memmove(dest, src, cnt) != dest) {
2896 psbs("can't create room for new characters");
2898 memset(p, ' ', size); // clear new hole
2899 end = end + size; // adjust the new END
2900 file_modified = TRUE; // has the file been modified
2905 // close a hole in text[]
2906 static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
2911 // move forwards, from beginning
2915 if (q < p) { // they are backward- swap them
2919 hole_size = q - p + 1;
2921 if (src < text || src > end)
2923 if (dest < text || dest >= end)
2926 goto thd_atend; // just delete the end of the buffer
2927 if (memmove(dest, src, cnt) != dest) {
2928 psbs("can't delete the character");
2931 end = end - hole_size; // adjust the new END
2933 dest = end - 1; // make sure dest in below end-1
2935 dest = end = text; // keep pointers valid
2936 file_modified = TRUE; // has the file been modified
2941 // copy text into register, then delete text.
2942 // if dist <= 0, do not include, or go past, a NewLine
2944 static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
2948 // make sure start <= stop
2950 // they are backwards, reverse them
2956 // we can not cross NL boundaries
2960 // dont go past a NewLine
2961 for (; p + 1 <= stop; p++) {
2963 stop = p; // "stop" just before NewLine
2969 #ifdef CONFIG_FEATURE_VI_YANKMARK
2970 text_yank(start, stop, YDreg);
2971 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2972 if (yf == YANKDEL) {
2973 p = text_hole_delete(start, stop);
2978 static void show_help(void)
2980 puts("These features are available:"
2981 #ifdef CONFIG_FEATURE_VI_SEARCH
2982 "\n\tPattern searches with / and ?"
2983 #endif /* CONFIG_FEATURE_VI_SEARCH */
2984 #ifdef CONFIG_FEATURE_VI_DOT_CMD
2985 "\n\tLast command repeat with \'.\'"
2986 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
2987 #ifdef CONFIG_FEATURE_VI_YANKMARK
2988 "\n\tLine marking with 'x"
2989 "\n\tNamed buffers with \"x"
2990 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2991 #ifdef CONFIG_FEATURE_VI_READONLY
2992 "\n\tReadonly if vi is called as \"view\""
2993 "\n\tReadonly with -R command line arg"
2994 #endif /* CONFIG_FEATURE_VI_READONLY */
2995 #ifdef CONFIG_FEATURE_VI_SET
2996 "\n\tSome colon mode commands with \':\'"
2997 #endif /* CONFIG_FEATURE_VI_SET */
2998 #ifdef CONFIG_FEATURE_VI_SETOPTS
2999 "\n\tSettable options with \":set\""
3000 #endif /* CONFIG_FEATURE_VI_SETOPTS */
3001 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
3002 "\n\tSignal catching- ^C"
3003 "\n\tJob suspend and resume with ^Z"
3004 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
3005 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
3006 "\n\tAdapt to window re-sizes"
3007 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
3011 static void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
3016 strcpy((char *) buf, ""); // init buf
3017 if (strlen((char *) s) <= 0)
3018 s = (Byte *) "(NULL)";
3019 for (; *s > '\0'; s++) {
3022 strcat((char *) buf, SOs);
3026 strcat((char *) buf, "^");
3030 strcat((char *) buf, (char *) b);
3032 strcat((char *) buf, SOn);
3034 strcat((char *) buf, "$");
3039 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3040 static void start_new_cmd_q(Byte c)
3043 if (last_modifying_cmd != 0)
3044 free(last_modifying_cmd);
3045 // get buffer for new cmd
3046 last_modifying_cmd = (Byte *) xmalloc(BUFSIZ);
3047 memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue
3048 // if there is a current cmd count put it in the buffer first
3050 sprintf((char *) last_modifying_cmd, "%d", cmdcnt);
3051 // save char c onto queue
3052 last_modifying_cmd[strlen((char *) last_modifying_cmd)] = c;
3057 static void end_cmd_q(void)
3059 #ifdef CONFIG_FEATURE_VI_YANKMARK
3060 YDreg = 26; // go back to default Yank/Delete reg
3061 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3065 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3067 #if defined(CONFIG_FEATURE_VI_YANKMARK) || defined(CONFIG_FEATURE_VI_COLON) || defined(CONFIG_FEATURE_VI_CRASHME)
3068 static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
3072 i = strlen((char *) s);
3073 p = text_hole_make(p, i);
3074 strncpy((char *) p, (char *) s, i);
3075 for (cnt = 0; *s != '\0'; s++) {
3079 #ifdef CONFIG_FEATURE_VI_YANKMARK
3080 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
3081 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3084 #endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */
3086 #ifdef CONFIG_FEATURE_VI_YANKMARK
3087 static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register
3092 if (q < p) { // they are backwards- reverse them
3099 if (t != 0) { // if already a yank register
3102 t = (Byte *) xmalloc(cnt + 1); // get a new register
3103 memset(t, '\0', cnt + 1); // clear new text[]
3104 strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer
3109 static Byte what_reg(void)
3115 c = 'D'; // default to D-reg
3116 if (0 <= YDreg && YDreg <= 25)
3117 c = 'a' + (Byte) YDreg;
3125 static void check_context(Byte cmd)
3127 // A context is defined to be "modifying text"
3128 // Any modifying command establishes a new context.
3130 if (dot < context_start || dot > context_end) {
3131 if (strchr((char *) modifying_cmds, cmd) != NULL) {
3132 // we are trying to modify text[]- make this the current context
3133 mark[27] = mark[26]; // move cur to prev
3134 mark[26] = dot; // move local to cur
3135 context_start = prev_line(prev_line(dot));
3136 context_end = next_line(next_line(dot));
3137 //loiter= start_loiter= now;
3143 static Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
3147 // the current context is in mark[26]
3148 // the previous context is in mark[27]
3149 // only swap context if other context is valid
3150 if (text <= mark[27] && mark[27] <= end - 1) {
3152 mark[27] = mark[26];
3154 p = mark[26]; // where we are going- previous context
3155 context_start = prev_line(prev_line(prev_line(p)));
3156 context_end = next_line(next_line(next_line(p)));
3160 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3162 static int isblnk(Byte c) // is the char a blank or tab
3164 return (c == ' ' || c == '\t');
3167 //----- Set terminal attributes --------------------------------
3168 static void rawmode(void)
3170 tcgetattr(0, &term_orig);
3171 term_vi = term_orig;
3172 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
3173 term_vi.c_iflag &= (~IXON & ~ICRNL);
3174 term_vi.c_oflag &= (~ONLCR);
3176 term_vi.c_cc[VMIN] = 1;
3177 term_vi.c_cc[VTIME] = 0;
3179 erase_char = term_vi.c_cc[VERASE];
3180 tcsetattr(0, TCSANOW, &term_vi);
3183 static void cookmode(void)
3185 tcsetattr(0, TCSANOW, &term_orig);
3188 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
3189 //----- See what the window size currently is --------------------
3190 static void window_size_get(int sig)
3194 i = ioctl(0, TIOCGWINSZ, &winsize);
3197 winsize.ws_row = 24;
3198 winsize.ws_col = 80;
3200 if (winsize.ws_row <= 1) {
3201 winsize.ws_row = 24;
3203 if (winsize.ws_col <= 1) {
3204 winsize.ws_col = 80;
3206 rows = (int) winsize.ws_row;
3207 columns = (int) winsize.ws_col;
3209 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
3211 //----- Come here when we get a window resize signal ---------
3212 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
3213 static void winch_sig(int sig)
3215 signal(SIGWINCH, winch_sig);
3216 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
3218 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
3219 new_screen(rows, columns); // get memory for virtual screen
3220 redraw(TRUE); // re-draw the screen
3223 //----- Come here when we get a continue signal -------------------
3224 static void cont_sig(int sig)
3226 rawmode(); // terminal to "raw"
3227 *status_buffer = '\0'; // clear the status buffer
3228 redraw(TRUE); // re-draw the screen
3230 signal(SIGTSTP, suspend_sig);
3231 signal(SIGCONT, SIG_DFL);
3232 kill(getpid(), SIGCONT);
3235 //----- Come here when we get a Suspend signal -------------------
3236 static void suspend_sig(int sig)
3238 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
3239 clear_to_eol(); // Erase to end of line
3240 cookmode(); // terminal to "cooked"
3242 signal(SIGCONT, cont_sig);
3243 signal(SIGTSTP, SIG_DFL);
3244 kill(getpid(), SIGTSTP);
3247 //----- Come here when we get a signal ---------------------------
3248 static void catch_sig(int sig)
3250 signal(SIGHUP, catch_sig);
3251 signal(SIGINT, catch_sig);
3252 signal(SIGTERM, catch_sig);
3253 longjmp(restart, sig);
3256 static void alarm_sig(int sig)
3258 signal(SIGALRM, catch_sig);
3259 longjmp(restart, sig);
3262 //----- Come here when we get a core dump signal -----------------
3263 static void core_sig(int sig)
3265 signal(SIGQUIT, core_sig);
3266 signal(SIGILL, core_sig);
3267 signal(SIGTRAP, core_sig);
3268 signal(SIGIOT, core_sig);
3269 signal(SIGABRT, core_sig);
3270 signal(SIGFPE, core_sig);
3271 signal(SIGBUS, core_sig);
3272 signal(SIGSEGV, core_sig);
3274 signal(SIGSYS, core_sig);
3277 dot = bound_dot(dot); // make sure "dot" is valid
3279 longjmp(restart, sig);
3281 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
3283 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
3285 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
3289 tv.tv_usec = hund * 10000;
3290 select(1, &rfds, NULL, NULL, &tv);
3291 return (FD_ISSET(0, &rfds));
3294 //----- IO Routines --------------------------------------------
3295 static Byte readit(void) // read (maybe cursor) key from stdin
3298 int i, bufsiz, cnt, cmdindex;
3304 static struct esc_cmds esccmds[] = {
3305 {(Byte *) "
\eOA", (Byte) VI_K_UP}, // cursor key Up
3306 {(Byte *) "
\eOB", (Byte) VI_K_DOWN}, // cursor key Down
3307 {(Byte *) "
\eOC", (Byte) VI_K_RIGHT}, // Cursor Key Right
3308 {(Byte *) "
\eOD", (Byte) VI_K_LEFT}, // cursor key Left
3309 {(Byte *) "
\eOH", (Byte) VI_K_HOME}, // Cursor Key Home
3310 {(Byte *) "
\eOF", (Byte) VI_K_END}, // Cursor Key End
3311 {(Byte *) "
\e[A", (Byte) VI_K_UP}, // cursor key Up
3312 {(Byte *) "
\e[B", (Byte) VI_K_DOWN}, // cursor key Down
3313 {(Byte *) "
\e[C", (Byte) VI_K_RIGHT}, // Cursor Key Right
3314 {(Byte *) "
\e[D", (Byte) VI_K_LEFT}, // cursor key Left
3315 {(Byte *) "
\e[H", (Byte) VI_K_HOME}, // Cursor Key Home
3316 {(Byte *) "
\e[F", (Byte) VI_K_END}, // Cursor Key End
3317 {(Byte *) "
\e[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert
3318 {(Byte *) "
\e[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up
3319 {(Byte *) "
\e[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down
3320 {(Byte *) "
\eOP", (Byte) VI_K_FUN1}, // Function Key F1
3321 {(Byte *) "
\eOQ", (Byte) VI_K_FUN2}, // Function Key F2
3322 {(Byte *) "
\eOR", (Byte) VI_K_FUN3}, // Function Key F3
3323 {(Byte *) "
\eOS", (Byte) VI_K_FUN4}, // Function Key F4
3324 {(Byte *) "
\e[15~", (Byte) VI_K_FUN5}, // Function Key F5
3325 {(Byte *) "
\e[17~", (Byte) VI_K_FUN6}, // Function Key F6
3326 {(Byte *) "
\e[18~", (Byte) VI_K_FUN7}, // Function Key F7
3327 {(Byte *) "
\e[19~", (Byte) VI_K_FUN8}, // Function Key F8
3328 {(Byte *) "
\e[20~", (Byte) VI_K_FUN9}, // Function Key F9
3329 {(Byte *) "
\e[21~", (Byte) VI_K_FUN10}, // Function Key F10
3330 {(Byte *) "
\e[23~", (Byte) VI_K_FUN11}, // Function Key F11
3331 {(Byte *) "
\e[24~", (Byte) VI_K_FUN12}, // Function Key F12
3332 {(Byte *) "
\e[11~", (Byte) VI_K_FUN1}, // Function Key F1
3333 {(Byte *) "
\e[12~", (Byte) VI_K_FUN2}, // Function Key F2
3334 {(Byte *) "
\e[13~", (Byte) VI_K_FUN3}, // Function Key F3
3335 {(Byte *) "
\e[14~", (Byte) VI_K_FUN4}, // Function Key F4
3338 #define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
3340 (void) alarm(0); // turn alarm OFF while we wait for input
3341 // get input from User- are there already input chars in Q?
3342 bufsiz = strlen((char *) readbuffer);
3345 // the Q is empty, wait for a typed char
3346 bufsiz = read(0, readbuffer, BUFSIZ - 1);
3349 goto ri0; // interrupted sys call
3352 if (errno == EFAULT)
3354 if (errno == EINVAL)
3361 readbuffer[bufsiz] = '\0';
3363 // return char if it is not part of ESC sequence
3364 if (readbuffer[0] != 27)
3367 // This is an ESC char. Is this Esc sequence?
3368 // Could be bare Esc key. See if there are any
3369 // more chars to read after the ESC. This would
3370 // be a Function or Cursor Key sequence.
3374 tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000
3376 // keep reading while there are input chars and room in buffer
3377 while (select(1, &rfds, NULL, NULL, &tv) > 0 && bufsiz <= (BUFSIZ - 5)) {
3378 // read the rest of the ESC string
3379 i = read(0, (void *) (readbuffer + bufsiz), BUFSIZ - bufsiz);
3382 readbuffer[bufsiz] = '\0'; // Terminate the string
3385 // Maybe cursor or function key?
3386 for (cmdindex = 0; cmdindex < ESCCMDS_COUNT; cmdindex++) {
3387 cnt = strlen((char *) esccmds[cmdindex].seq);
3388 i = strncmp((char *) esccmds[cmdindex].seq, (char *) readbuffer, cnt);
3390 // is a Cursor key- put derived value back into Q
3391 readbuffer[0] = esccmds[cmdindex].val;
3392 // squeeze out the ESC sequence
3393 for (i = 1; i < cnt; i++) {
3394 memmove(readbuffer + 1, readbuffer + 2, BUFSIZ - 2);
3395 readbuffer[BUFSIZ - 1] = '\0';
3402 // remove one char from Q
3403 memmove(readbuffer, readbuffer + 1, BUFSIZ - 1);
3404 readbuffer[BUFSIZ - 1] = '\0';
3405 (void) alarm(3); // we are done waiting for input, turn alarm ON
3409 //----- IO Routines --------------------------------------------
3410 static Byte get_one_char()
3414 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3415 // ! adding2q && ioq == 0 read()
3416 // ! adding2q && ioq != 0 *ioq
3417 // adding2q *last_modifying_cmd= read()
3419 // we are not adding to the q.
3420 // but, we may be reading from a q
3422 // there is no current q, read from STDIN
3423 c = readit(); // get the users input
3425 // there is a queue to get chars from first
3428 // the end of the q, read from STDIN
3430 ioq_start = ioq = 0;
3431 c = readit(); // get the users input
3435 // adding STDIN chars to q
3436 c = readit(); // get the users input
3437 if (last_modifying_cmd != 0) {
3438 // add new char to q
3439 last_modifying_cmd[strlen((char *) last_modifying_cmd)] = c;
3442 #else /* CONFIG_FEATURE_VI_DOT_CMD */
3443 c = readit(); // get the users input
3444 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3445 return (c); // return the char, where ever it came from
3448 static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
3453 static Byte *obufp = NULL;
3455 strcpy((char *) buf, (char *) prompt);
3456 *status_buffer = '\0'; // clear the status buffer
3457 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
3458 clear_to_eol(); // clear the line
3459 write(1, prompt, strlen((char *) prompt)); // write out the :, /, or ? prompt
3461 for (i = strlen((char *) buf); i < BUFSIZ;) {
3462 c = get_one_char(); // read user input
3463 if (c == '\n' || c == '\r' || c == 27)
3464 break; // is this end of input
3465 if (c == erase_char) { // user wants to erase prev char
3466 i--; // backup to prev char
3467 buf[i] = '\0'; // erase the char
3468 buf[i + 1] = '\0'; // null terminate buffer
3469 write(1, "
\b \b", 3); // erase char on screen
3470 if (i <= 0) { // user backs up before b-o-l, exit
3474 buf[i] = c; // save char in buffer
3475 buf[i + 1] = '\0'; // make sure buffer is null terminated
3476 write(1, buf + i, 1); // echo the char back to user
3483 obufp = (Byte *) xstrdup((char *) buf);
3487 static int file_size(Byte * fn) // what is the byte size of "fn"
3492 if (fn == 0 || strlen(fn) <= 0)
3495 sr = stat((char *) fn, &st_buf); // see if file exists
3497 cnt = (int) st_buf.st_size;
3502 static int file_insert(Byte * fn, Byte * p, int size)
3507 #ifdef CONFIG_FEATURE_VI_READONLY
3509 #endif /* CONFIG_FEATURE_VI_READONLY */
3510 if (fn == 0 || strlen((char*) fn) <= 0) {
3511 psbs("No filename given");
3515 // OK- this is just a no-op
3520 psbs("Trying to insert a negative number (%d) of characters", size);
3523 if (p < text || p > end) {
3524 psbs("Trying to insert file outside of memory");
3528 // see if we can open the file
3529 #ifdef CONFIG_FEATURE_VI_READONLY
3530 if (vi_readonly) goto fi1; // do not try write-mode
3532 fd = open((char *) fn, O_RDWR); // assume read & write
3534 // could not open for writing- maybe file is read only
3535 #ifdef CONFIG_FEATURE_VI_READONLY
3538 fd = open((char *) fn, O_RDONLY); // try read-only
3540 psbs("\"%s\" %s", fn, "could not open file");
3543 #ifdef CONFIG_FEATURE_VI_READONLY
3544 // got the file- read-only
3546 #endif /* CONFIG_FEATURE_VI_READONLY */
3548 p = text_hole_make(p, size);
3549 cnt = read(fd, p, size);
3553 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
3554 psbs("could not read file \"%s\"", fn);
3555 } else if (cnt < size) {
3556 // There was a partial read, shrink unused space text[]
3557 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
3558 psbs("could not read all of file \"%s\"", fn);
3561 file_modified = TRUE;
3566 static int file_write(Byte * fn, Byte * first, Byte * last)
3568 int fd, cnt, charcnt;
3571 psbs("No current filename");
3575 // FIXIT- use the correct umask()
3576 fd = open((char *) fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664);
3579 cnt = last - first + 1;
3580 charcnt = write(fd, first, cnt);
3581 if (charcnt == cnt) {
3583 //file_modified= FALSE; // the file has not been modified
3591 //----- Terminal Drawing ---------------------------------------
3592 // The terminal is made up of 'rows' line of 'columns' columns.
3593 // classicly this would be 24 x 80.
3594 // screen coordinates
3600 // 23,0 ... 23,79 status line
3603 //----- Move the cursor to row x col (count from 0, not 1) -------
3604 static void place_cursor(int row, int col, int opti)
3609 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
3612 // char cm3[BUFSIZ];
3614 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
3616 memset(cm1, '\0', BUFSIZ - 1); // clear the buffer
3618 if (row < 0) row = 0;
3619 if (row >= rows) row = rows - 1;
3620 if (col < 0) col = 0;
3621 if (col >= columns) col = columns - 1;
3623 //----- 1. Try the standard terminal ESC sequence
3624 sprintf((char *) cm1, CMrc, row + 1, col + 1);
3626 if (! opti) goto pc0;
3628 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
3629 //----- find the minimum # of chars to move cursor -------------
3630 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
3631 memset(cm2, '\0', BUFSIZ - 1); // clear the buffer
3633 // move to the correct row
3634 while (row < Rrow) {
3635 // the cursor has to move up
3639 while (row > Rrow) {
3640 // the cursor has to move down
3641 strcat(cm2, CMdown);
3645 // now move to the correct column
3646 strcat(cm2, "\r"); // start at col 0
3647 // just send out orignal source char to get to correct place
3648 screenp = &screen[row * columns]; // start of screen line
3649 strncat(cm2, screenp, col);
3651 //----- 3. Try some other way of moving cursor
3652 //---------------------------------------------
3654 // pick the shortest cursor motion to send out
3656 if (strlen(cm2) < strlen(cm)) {
3658 } /* else if (strlen(cm3) < strlen(cm)) {
3661 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
3664 if (l) write(1, cm, l); // move the cursor
3667 //----- Erase from cursor to end of line -----------------------
3668 static void clear_to_eol()
3670 write(1, Ceol, strlen(Ceol)); // Erase from cursor to end of line
3673 //----- Erase from cursor to end of screen -----------------------
3674 static void clear_to_eos()
3676 write(1, Ceos, strlen(Ceos)); // Erase from cursor to end of screen
3679 //----- Start standout mode ------------------------------------
3680 static void standout_start() // send "start reverse video" sequence
3682 write(1, SOs, strlen(SOs)); // Start reverse video mode
3685 //----- End standout mode --------------------------------------
3686 static void standout_end() // send "end reverse video" sequence
3688 write(1, SOn, strlen(SOn)); // End reverse video mode
3691 //----- Flash the screen --------------------------------------
3692 static void flash(int h)
3694 standout_start(); // send "start reverse video" sequence
3697 standout_end(); // send "end reverse video" sequence
3703 write(1, bell, strlen(bell)); // send out a bell character
3706 static void indicate_error(char c)
3708 #ifdef CONFIG_FEATURE_VI_CRASHME
3710 return; // generate a random command
3711 #endif /* CONFIG_FEATURE_VI_CRASHME */
3712 if (err_method == 0) {
3719 //----- Screen[] Routines --------------------------------------
3720 //----- Erase the Screen[] memory ------------------------------
3721 static void screen_erase()
3723 memset(screen, ' ', screensize); // clear new screen
3726 //----- Draw the status line at bottom of the screen -------------
3727 static void show_status_line(void)
3729 static int last_cksum;
3732 cnt = strlen((char *) status_buffer);
3733 for (cksum= l= 0; l < cnt; l++) { cksum += (int)(status_buffer[l]); }
3734 // don't write the status line unless it changes
3735 if (cnt > 0 && last_cksum != cksum) {
3736 last_cksum= cksum; // remember if we have seen this line
3737 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
3738 write(1, status_buffer, cnt);
3740 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
3744 //----- format the status buffer, the bottom line of screen ------
3745 // print status buffer, with STANDOUT mode
3746 static void psbs(char *format, ...)
3750 va_start(args, format);
3751 strcpy((char *) status_buffer, SOs); // Terminal standout mode on
3752 vsprintf((char *) status_buffer + strlen((char *) status_buffer), format,
3754 strcat((char *) status_buffer, SOn); // Terminal standout mode off
3760 // print status buffer
3761 static void psb(char *format, ...)
3765 va_start(args, format);
3766 vsprintf((char *) status_buffer, format, args);
3771 static void ni(Byte * s) // display messages
3775 print_literal(buf, s);
3776 psbs("\'%s\' is not implemented", buf);
3779 static void edit_status(void) // show file status on status line
3781 int cur, tot, percent;
3783 cur = count_lines(text, dot);
3784 tot = count_lines(text, end - 1);
3785 // current line percent
3786 // ------------- ~~ ----------
3789 percent = (100 * cur) / tot;
3795 #ifdef CONFIG_FEATURE_VI_READONLY
3797 #endif /* CONFIG_FEATURE_VI_READONLY */
3798 "%s line %d of %d --%d%%--",
3799 (cfn != 0 ? (char *) cfn : "No file"),
3800 #ifdef CONFIG_FEATURE_VI_READONLY
3801 ((vi_readonly || readonly) ? " [Read only]" : ""),
3802 #endif /* CONFIG_FEATURE_VI_READONLY */
3803 (file_modified ? " [modified]" : ""),
3807 //----- Force refresh of all Lines -----------------------------
3808 static void redraw(int full_screen)
3810 place_cursor(0, 0, FALSE); // put cursor in correct place
3811 clear_to_eos(); // tel terminal to erase display
3812 screen_erase(); // erase the internal screen buffer
3813 refresh(full_screen); // this will redraw the entire display
3816 //----- Format a text[] line into a buffer ---------------------
3817 static void format_line(Byte *dest, Byte *src, int li)
3822 for (co= 0; co < MAX_SCR_COLS; co++) {
3823 c= ' '; // assume blank
3824 if (li > 0 && co == 0) {
3825 c = '~'; // not first line, assume Tilde
3827 // are there chars in text[] and have we gone past the end
3828 if (text < end && src < end) {
3833 if (c < ' ' || c > '~') {
3837 for (; (co % tabstop) != (tabstop - 1); co++) {
3842 c |= '@'; // make it visible
3843 c &= 0x7f; // get rid of hi bit
3846 // the co++ is done here so that the column will
3847 // not be overwritten when we blank-out the rest of line
3854 //----- Refresh the changed screen lines -----------------------
3855 // Copy the source line from text[] into the buffer and note
3856 // if the current screenline is different from the new buffer.
3857 // If they differ then that line needs redrawing on the terminal.
3859 static void refresh(int full_screen)
3861 static int old_offset;
3863 Byte buf[MAX_SCR_COLS];
3864 Byte *tp, *sp; // pointer into text[] and screen[]
3865 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
3866 int last_li= -2; // last line that changed- for optimizing cursor movement
3867 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
3869 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
3871 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
3872 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
3873 tp = screenbegin; // index into text[] of top line
3875 // compare text[] to screen[] and mark screen[] lines that need updating
3876 for (li = 0; li < rows - 1; li++) {
3877 int cs, ce; // column start & end
3878 memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer
3879 buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer
3880 // format current text line into buf
3881 format_line(buf, tp, li);
3883 // skip to the end of the current text[] line
3884 while (tp < end && *tp++ != '\n') /*no-op*/ ;
3886 // see if there are any changes between vitual screen and buf
3887 changed = FALSE; // assume no change
3890 sp = &screen[li * columns]; // start of screen line
3892 // force re-draw of every single column from 0 - columns-1
3895 // compare newly formatted buffer with virtual screen
3896 // look forward for first difference between buf and screen
3897 for ( ; cs <= ce; cs++) {
3898 if (buf[cs + offset] != sp[cs]) {
3899 changed = TRUE; // mark for redraw
3904 // look backward for last difference between buf and screen
3905 for ( ; ce >= cs; ce--) {
3906 if (buf[ce + offset] != sp[ce]) {
3907 changed = TRUE; // mark for redraw
3911 // now, cs is index of first diff, and ce is index of last diff
3913 // if horz offset has changed, force a redraw
3914 if (offset != old_offset) {
3919 // make a sanity check of columns indexes
3921 if (ce > columns-1) ce= columns-1;
3922 if (cs > ce) { cs= 0; ce= columns-1; }
3923 // is there a change between vitual screen and buf
3925 // copy changed part of buffer to virtual screen
3926 memmove(sp+cs, buf+(cs+offset), ce-cs+1);
3928 // move cursor to column of first change
3929 if (offset != old_offset) {
3930 // opti_cur_move is still too stupid
3931 // to handle offsets correctly
3932 place_cursor(li, cs, FALSE);
3934 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
3935 // if this just the next line
3936 // try to optimize cursor movement
3937 // otherwise, use standard ESC sequence
3938 place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE);
3940 #else /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
3941 place_cursor(li, cs, FALSE); // use standard ESC sequence
3942 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
3945 // write line out to terminal
3946 write(1, sp+cs, ce-cs+1);
3947 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
3949 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
3953 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
3954 place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE);
3957 place_cursor(crow, ccol, FALSE);
3958 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
3960 if (offset != old_offset)
3961 old_offset = offset;