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.22 2002/07/31 21:22:21 sandman 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 #if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK)
346 modifying_cmds = (Byte *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[]
347 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
349 // 1- process $HOME/.exrc file
350 // 2- process EXINIT variable from environment
351 // 3- process command line args
352 while ((c = getopt(argc, argv, "hCR")) != -1) {
354 #ifdef CONFIG_FEATURE_VI_CRASHME
358 #endif /* CONFIG_FEATURE_VI_CRASHME */
359 #ifdef CONFIG_FEATURE_VI_READONLY
360 case 'R': // Read-only flag
363 #endif /* CONFIG_FEATURE_VI_READONLY */
364 //case 'r': // recover flag- ignore- we don't use tmp file
365 //case 'x': // encryption flag- ignore
366 //case 'c': // execute command first
367 //case 'h': // help -- just use default
374 // The argv array can be used by the ":next" and ":rewind" commands
376 fn_start = optind; // remember first file name for :next and :rew
379 //----- This is the main file handling loop --------------
380 if (optind >= argc) {
381 editing = 1; // 0= exit, 1= one file, 2= multiple files
384 for (; optind < argc; optind++) {
385 editing = 1; // 0=exit, 1=one file, 2+ =many files
388 cfn = (Byte *) xstrdup(argv[optind]);
392 //-----------------------------------------------------------
397 static void edit_file(Byte * fn)
402 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
405 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
406 #ifdef CONFIG_FEATURE_VI_YANKMARK
407 static Byte *cur_line;
408 #endif /* CONFIG_FEATURE_VI_YANKMARK */
414 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
416 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
417 new_screen(rows, columns); // get memory for virtual screen
419 cnt = file_size(fn); // file size
420 size = 2 * cnt; // 200% of file size
421 new_text(size); // get a text[] buffer
422 screenbegin = dot = end = text;
424 ch= file_insert(fn, text, cnt);
427 (void) char_insert(text, '\n'); // start empty buf with dummy line
429 file_modified = FALSE;
430 #ifdef CONFIG_FEATURE_VI_YANKMARK
431 YDreg = 26; // default Yank/Delete reg
432 Ureg = 27; // hold orig line for "U" cmd
433 for (cnt = 0; cnt < 28; cnt++) {
436 mark[26] = mark[27] = text; // init "previous context"
437 #endif /* CONFIG_FEATURE_VI_YANKMARK */
439 err_method = 1; // flash
440 last_forward_char = last_input_char = '\0';
445 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
446 signal(SIGHUP, catch_sig);
447 signal(SIGINT, catch_sig);
448 signal(SIGALRM, alarm_sig);
449 signal(SIGTERM, catch_sig);
450 signal(SIGQUIT, core_sig);
451 signal(SIGILL, core_sig);
452 signal(SIGTRAP, core_sig);
453 signal(SIGIOT, core_sig);
454 signal(SIGABRT, core_sig);
455 signal(SIGFPE, core_sig);
456 signal(SIGBUS, core_sig);
457 signal(SIGSEGV, core_sig);
459 signal(SIGSYS, core_sig);
461 signal(SIGWINCH, winch_sig);
462 signal(SIGTSTP, suspend_sig);
463 sig = setjmp(restart);
467 msg = "(window resize)";
477 msg = "(I tried to touch invalid memory)";
481 psbs("-- caught signal %d %s--", sig, msg);
482 screenbegin = dot = text;
484 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
487 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
490 offset = 0; // no horizontal offset
492 #ifdef CONFIG_FEATURE_VI_DOT_CMD
493 if (last_modifying_cmd != 0)
494 free(last_modifying_cmd);
495 if (ioq_start != NULL)
497 ioq = ioq_start = last_modifying_cmd = 0;
499 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
500 redraw(FALSE); // dont force every col re-draw
503 //------This is the main Vi cmd handling loop -----------------------
504 while (editing > 0) {
505 #ifdef CONFIG_FEATURE_VI_CRASHME
507 if ((end - text) > 1) {
508 crash_dummy(); // generate a random command
512 string_insert(text, (Byte *) "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
516 #endif /* CONFIG_FEATURE_VI_CRASHME */
517 last_input_char = c = get_one_char(); // get a cmd from user
518 #ifdef CONFIG_FEATURE_VI_YANKMARK
519 // save a copy of the current line- for the 'U" command
520 if (begin_line(dot) != cur_line) {
521 cur_line = begin_line(dot);
522 text_yank(begin_line(dot), end_line(dot), Ureg);
524 #endif /* CONFIG_FEATURE_VI_YANKMARK */
525 #ifdef CONFIG_FEATURE_VI_DOT_CMD
526 // These are commands that change text[].
527 // Remember the input for the "." command
528 if (!adding2q && ioq_start == 0
529 && strchr((char *) modifying_cmds, c) != NULL) {
532 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
533 do_cmd(c); // execute the user command
535 // poll to see if there is input already waiting. if we are
536 // not able to display output fast enough to keep up, skip
537 // the display update until we catch up with input.
538 if (mysleep(0) == 0) {
539 // no input pending- so update output
543 #ifdef CONFIG_FEATURE_VI_CRASHME
545 crash_test(); // test editor variables
546 #endif /* CONFIG_FEATURE_VI_CRASHME */
548 //-------------------------------------------------------------------
550 place_cursor(rows, 0, FALSE); // go to bottom of screen
551 clear_to_eol(); // Erase to end of line
555 static Byte readbuffer[BUFSIZ];
557 #ifdef CONFIG_FEATURE_VI_CRASHME
558 static int totalcmds = 0;
559 static int Mp = 85; // Movement command Probability
560 static int Np = 90; // Non-movement command Probability
561 static int Dp = 96; // Delete command Probability
562 static int Ip = 97; // Insert command Probability
563 static int Yp = 98; // Yank command Probability
564 static int Pp = 99; // Put command Probability
565 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
566 char chars[20] = "\t012345 abcdABCD-=.$";
567 char *words[20] = { "this", "is", "a", "test",
568 "broadcast", "the", "emergency", "of",
569 "system", "quick", "brown", "fox",
570 "jumped", "over", "lazy", "dogs",
571 "back", "January", "Febuary", "March"
574 "You should have received a copy of the GNU General Public License\n",
575 "char c, cm, *cmd, *cmd1;\n",
576 "generate a command by percentages\n",
577 "Numbers may be typed as a prefix to some commands.\n",
578 "Quit, discarding changes!\n",
579 "Forced write, if permission originally not valid.\n",
580 "In general, any ex or ed command (such as substitute or delete).\n",
581 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
582 "Please get w/ me and I will go over it with you.\n",
583 "The following is a list of scheduled, committed changes.\n",
584 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
585 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
586 "Any question about transactions please contact Sterling Huxley.\n",
587 "I will try to get back to you by Friday, December 31.\n",
588 "This Change will be implemented on Friday.\n",
589 "Let me know if you have problems accessing this;\n",
590 "Sterling Huxley recently added you to the access list.\n",
591 "Would you like to go to lunch?\n",
592 "The last command will be automatically run.\n",
593 "This is too much english for a computer geek.\n",
595 char *multilines[20] = {
596 "You should have received a copy of the GNU General Public License\n",
597 "char c, cm, *cmd, *cmd1;\n",
598 "generate a command by percentages\n",
599 "Numbers may be typed as a prefix to some commands.\n",
600 "Quit, discarding changes!\n",
601 "Forced write, if permission originally not valid.\n",
602 "In general, any ex or ed command (such as substitute or delete).\n",
603 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
604 "Please get w/ me and I will go over it with you.\n",
605 "The following is a list of scheduled, committed changes.\n",
606 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
607 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
608 "Any question about transactions please contact Sterling Huxley.\n",
609 "I will try to get back to you by Friday, December 31.\n",
610 "This Change will be implemented on Friday.\n",
611 "Let me know if you have problems accessing this;\n",
612 "Sterling Huxley recently added you to the access list.\n",
613 "Would you like to go to lunch?\n",
614 "The last command will be automatically run.\n",
615 "This is too much english for a computer geek.\n",
618 // create a random command to execute
619 static void crash_dummy()
621 static int sleeptime; // how long to pause between commands
622 char c, cm, *cmd, *cmd1;
623 int i, cnt, thing, rbi, startrbi, percent;
625 // "dot" movement commands
626 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
628 // is there already a command running?
629 if (strlen((char *) readbuffer) > 0)
633 sleeptime = 0; // how long to pause between commands
634 memset(readbuffer, '\0', BUFSIZ - 1); // clear the read buffer
635 // generate a command by percentages
636 percent = (int) lrand48() % 100; // get a number from 0-99
637 if (percent < Mp) { // Movement commands
638 // available commands
641 } else if (percent < Np) { // non-movement commands
642 cmd = "mz<>\'\""; // available commands
644 } else if (percent < Dp) { // Delete commands
645 cmd = "dx"; // available commands
647 } else if (percent < Ip) { // Inset commands
648 cmd = "iIaAsrJ"; // available commands
650 } else if (percent < Yp) { // Yank commands
651 cmd = "yY"; // available commands
653 } else if (percent < Pp) { // Put commands
654 cmd = "pP"; // available commands
657 // We do not know how to handle this command, try again
661 // randomly pick one of the available cmds from "cmd[]"
662 i = (int) lrand48() % strlen(cmd);
664 if (strchr(":\024", cm))
665 goto cd0; // dont allow colon or ctrl-T commands
666 readbuffer[rbi++] = cm; // put cmd into input buffer
668 // now we have the command-
669 // there are 1, 2, and multi char commands
670 // find out which and generate the rest of command as necessary
671 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
672 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
673 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
674 cmd1 = "abcdefghijklmnopqrstuvwxyz";
676 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
678 readbuffer[rbi++] = c; // add movement to input buffer
680 if (strchr("iIaAsc", cm)) { // multi-char commands
683 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
685 readbuffer[rbi++] = c; // add movement to input buffer
687 thing = (int) lrand48() % 4; // what thing to insert
688 cnt = (int) lrand48() % 10; // how many to insert
689 for (i = 0; i < cnt; i++) {
690 if (thing == 0) { // insert chars
691 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
692 } else if (thing == 1) { // insert words
693 strcat((char *) readbuffer, words[(int) lrand48() % 20]);
694 strcat((char *) readbuffer, " ");
695 sleeptime = 0; // how fast to type
696 } else if (thing == 2) { // insert lines
697 strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
698 sleeptime = 0; // how fast to type
699 } else { // insert multi-lines
700 strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
701 sleeptime = 0; // how fast to type
704 strcat((char *) readbuffer, "\033");
709 (void) mysleep(sleeptime); // sleep 1/100 sec
712 // test to see if there are any errors
713 static void crash_test()
715 static time_t oldtim;
717 char d[2], buf[BUFSIZ], msg[BUFSIZ];
721 strcat((char *) msg, "end<text ");
724 strcat((char *) msg, "end>textend ");
727 strcat((char *) msg, "dot<text ");
730 strcat((char *) msg, "dot>end ");
732 if (screenbegin < text) {
733 strcat((char *) msg, "screenbegin<text ");
735 if (screenbegin > end - 1) {
736 strcat((char *) msg, "screenbegin>end-1 ");
739 if (strlen(msg) > 0) {
741 sprintf(buf, "\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
742 totalcmds, last_input_char, msg, SOs, SOn);
743 write(1, buf, strlen(buf));
744 while (read(0, d, 1) > 0) {
745 if (d[0] == '\n' || d[0] == '\r')
750 tim = (time_t) time((time_t *) 0);
751 if (tim >= (oldtim + 3)) {
752 sprintf((char *) status_buffer,
753 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
754 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
759 #endif /* CONFIG_FEATURE_VI_CRASHME */
761 //---------------------------------------------------------------------
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 strncasecmp((char *) p, "x", cnt) == 0) {
1230 cnt = file_write(cfn, text, end - 1);
1231 file_modified = FALSE;
1232 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
1233 if (p[0] == 'x' || p[1] == 'q') {
1236 } else if (strncasecmp((char *) p, "file", cnt) == 0 ) {
1237 edit_status(); // show current file status
1238 } else if (sscanf((char *) p, "%d", &j) > 0) {
1239 dot = find_line(j); // go to line # j
1241 } else { // unrecognised cmd
1244 #endif /* CONFIG_FEATURE_VI_COLON */
1246 case '<': // <- Left shift something
1247 case '>': // >- Right shift something
1248 cnt = count_lines(text, dot); // remember what line we are on
1249 c1 = get_one_char(); // get the type of thing to delete
1250 find_range(&p, &q, c1);
1251 (void) yank_delete(p, q, 1, YANKONLY); // save copy before change
1254 i = count_lines(p, q); // # of lines we are shifting
1255 for ( ; i > 0; i--, p = next_line(p)) {
1257 // shift left- remove tab or 8 spaces
1259 // shrink buffer 1 char
1260 (void) text_hole_delete(p, p);
1261 } else if (*p == ' ') {
1262 // we should be calculating columns, not just SPACE
1263 for (j = 0; *p == ' ' && j < tabstop; j++) {
1264 (void) text_hole_delete(p, p);
1267 } else if (c == '>') {
1268 // shift right -- add tab or 8 spaces
1269 (void) char_insert(p, '\t');
1272 dot = find_line(cnt); // what line were we on
1274 end_cmd_q(); // stop adding to q
1276 case 'A': // A- append at e-o-l
1277 dot_end(); // go to e-o-l
1278 //**** fall thru to ... 'a'
1279 case 'a': // a- append after current char
1284 case 'B': // B- back a blank-delimited Word
1285 case 'E': // E- end of a blank-delimited word
1286 case 'W': // W- forward a blank-delimited word
1293 if (c == 'W' || isspace(dot[dir])) {
1294 dot = skip_thing(dot, 1, dir, S_TO_WS);
1295 dot = skip_thing(dot, 2, dir, S_OVER_WS);
1298 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
1300 case 'C': // C- Change to e-o-l
1301 case 'D': // D- delete to e-o-l
1303 dot = dollar_line(dot); // move to before NL
1304 // copy text into a register and delete
1305 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
1307 goto dc_i; // start inserting
1308 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1310 end_cmd_q(); // stop adding to q
1311 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
1313 case 'G': // G- goto to a line number (default= E-O-F)
1314 dot = end - 1; // assume E-O-F
1316 dot = find_line(cmdcnt); // what line is #cmdcnt
1320 case 'H': // H- goto top line on screen
1322 if (cmdcnt > (rows - 1)) {
1323 cmdcnt = (rows - 1);
1330 case 'I': // I- insert before first non-blank
1333 //**** fall thru to ... 'i'
1334 case 'i': // i- insert before current char
1335 case VI_K_INSERT: // Cursor Key Insert
1337 cmd_mode = 1; // start insrting
1338 psb("-- Insert --");
1340 case 'J': // J- join current and next lines together
1344 dot_end(); // move to NL
1345 if (dot < end - 1) { // make sure not last char in text[]
1346 *dot++ = ' '; // replace NL with space
1347 while (isblnk(*dot)) { // delete leading WS
1351 end_cmd_q(); // stop adding to q
1353 case 'L': // L- goto bottom line on screen
1355 if (cmdcnt > (rows - 1)) {
1356 cmdcnt = (rows - 1);
1364 case 'M': // M- goto middle line on screen
1366 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
1367 dot = next_line(dot);
1369 case 'O': // O- open a empty line above
1371 p = begin_line(dot);
1372 if (p[-1] == '\n') {
1374 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
1376 dot = char_insert(dot, '\n');
1379 dot = char_insert(dot, '\n'); // i\n ESC
1384 case 'R': // R- continuous Replace char
1387 psb("-- Replace --");
1389 case 'X': // X- delete char before dot
1390 case 'x': // x- delete the current char
1391 case 's': // s- substitute the current char
1398 if (dot[dir] != '\n') {
1400 dot--; // delete prev char
1401 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
1404 goto dc_i; // start insrting
1405 end_cmd_q(); // stop adding to q
1407 case 'Z': // Z- if modified, {write}; exit
1408 // ZZ means to save file (if necessary), then exit
1409 c1 = get_one_char();
1415 #ifdef CONFIG_FEATURE_VI_READONLY
1418 #endif /* CONFIG_FEATURE_VI_READONLY */
1420 cnt = file_write(cfn, text, end - 1);
1421 if (cnt == (end - 1 - text + 1)) {
1428 case '^': // ^- move to first non-blank on line
1432 case 'b': // b- back a word
1433 case 'e': // e- end of word
1440 if ((dot + dir) < text || (dot + dir) > end - 1)
1443 if (isspace(*dot)) {
1444 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
1446 if (isalnum(*dot) || *dot == '_') {
1447 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
1448 } else if (ispunct(*dot)) {
1449 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
1452 case 'c': // c- change something
1453 case 'd': // d- delete something
1454 #ifdef CONFIG_FEATURE_VI_YANKMARK
1455 case 'y': // y- yank something
1456 case 'Y': // Y- Yank a line
1457 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1458 yf = YANKDEL; // assume either "c" or "d"
1459 #ifdef CONFIG_FEATURE_VI_YANKMARK
1460 if (c == 'y' || c == 'Y')
1462 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1465 c1 = get_one_char(); // get the type of thing to delete
1466 find_range(&p, &q, c1);
1467 if (c1 == 27) { // ESC- user changed mind and wants out
1468 c = c1 = 27; // Escape- do nothing
1469 } else if (strchr("wW", c1)) {
1471 // don't include trailing WS as part of word
1472 while (isblnk(*q)) {
1473 if (q <= text || q[-1] == '\n')
1478 dot = yank_delete(p, q, 0, yf); // delete word
1479 } else if (strchr("^0bBeEft$", c1)) {
1480 // single line copy text into a register and delete
1481 dot = yank_delete(p, q, 0, yf); // delete word
1482 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
1483 // multiple line copy text into a register and delete
1484 dot = yank_delete(p, q, 1, yf); // delete lines
1486 dot = char_insert(dot, '\n');
1487 // on the last line of file don't move to prev line
1488 if (dot != (end-1)) {
1491 } else if (c == 'd') {
1496 // could not recognize object
1497 c = c1 = 27; // error-
1501 // if CHANGING, not deleting, start inserting after the delete
1503 strcpy((char *) buf, "Change");
1504 goto dc_i; // start inserting
1507 strcpy((char *) buf, "Delete");
1509 #ifdef CONFIG_FEATURE_VI_YANKMARK
1510 if (c == 'y' || c == 'Y') {
1511 strcpy((char *) buf, "Yank");
1514 q = p + strlen((char *) p);
1515 for (cnt = 0; p <= q; p++) {
1519 psb("%s %d lines (%d chars) using [%c]",
1520 buf, cnt, strlen((char *) reg[YDreg]), what_reg());
1521 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1522 end_cmd_q(); // stop adding to q
1525 case 'k': // k- goto prev line, same col
1526 case VI_K_UP: // cursor key Up
1531 dot = move_to_col(dot, ccol + offset); // try stay in same col
1533 case 'r': // r- replace the current char with user input
1534 c1 = get_one_char(); // get the replacement char
1537 file_modified = TRUE; // has the file been modified
1539 end_cmd_q(); // stop adding to q
1541 case 't': // t- move to char prior to next x
1542 last_forward_char = get_one_char();
1544 if (*dot == last_forward_char)
1546 last_forward_char= 0;
1548 case 'w': // w- forward a word
1552 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
1553 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
1554 } else if (ispunct(*dot)) { // we are on PUNCT
1555 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
1558 dot++; // move over word
1559 if (isspace(*dot)) {
1560 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
1564 c1 = get_one_char(); // get the replacement char
1567 cnt = (rows - 2) / 2; // put dot at center
1569 cnt = rows - 2; // put dot at bottom
1570 screenbegin = begin_line(dot); // start dot at top
1571 dot_scroll(cnt, -1);
1573 case '|': // |- move to column "cmdcnt"
1574 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
1576 case '~': // ~- flip the case of letters a-z -> A-Z
1580 if (islower(*dot)) {
1581 *dot = toupper(*dot);
1582 file_modified = TRUE; // has the file been modified
1583 } else if (isupper(*dot)) {
1584 *dot = tolower(*dot);
1585 file_modified = TRUE; // has the file been modified
1588 end_cmd_q(); // stop adding to q
1590 //----- The Cursor and Function Keys -----------------------------
1591 case VI_K_HOME: // Cursor Key Home
1594 // The Fn keys could point to do_macro which could translate them
1595 case VI_K_FUN1: // Function Key F1
1596 case VI_K_FUN2: // Function Key F2
1597 case VI_K_FUN3: // Function Key F3
1598 case VI_K_FUN4: // Function Key F4
1599 case VI_K_FUN5: // Function Key F5
1600 case VI_K_FUN6: // Function Key F6
1601 case VI_K_FUN7: // Function Key F7
1602 case VI_K_FUN8: // Function Key F8
1603 case VI_K_FUN9: // Function Key F9
1604 case VI_K_FUN10: // Function Key F10
1605 case VI_K_FUN11: // Function Key F11
1606 case VI_K_FUN12: // Function Key F12
1611 // if text[] just became empty, add back an empty line
1613 (void) char_insert(text, '\n'); // start empty buf with dummy line
1616 // it is OK for dot to exactly equal to end, otherwise check dot validity
1618 dot = bound_dot(dot); // make sure "dot" is valid
1620 #ifdef CONFIG_FEATURE_VI_YANKMARK
1621 check_context(c); // update the current context
1622 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1625 cmdcnt = 0; // cmd was not a number, reset cmdcnt
1626 cnt = dot - begin_line(dot);
1627 // Try to stay off of the Newline
1628 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
1632 //----- The Colon commands -------------------------------------
1633 #ifdef CONFIG_FEATURE_VI_COLON
1634 static Byte *get_one_address(Byte * p, int *addr) // get colon addr, if present
1639 #ifdef CONFIG_FEATURE_VI_YANKMARK
1641 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1642 #ifdef CONFIG_FEATURE_VI_SEARCH
1643 Byte *pat, buf[BUFSIZ];
1644 #endif /* CONFIG_FEATURE_VI_SEARCH */
1646 *addr = -1; // assume no addr
1647 if (*p == '.') { // the current line
1649 q = begin_line(dot);
1650 *addr = count_lines(text, q);
1651 #ifdef CONFIG_FEATURE_VI_YANKMARK
1652 } else if (*p == '\'') { // is this a mark addr
1656 if (c >= 'a' && c <= 'z') {
1660 if (q != NULL) { // is mark valid
1661 *addr = count_lines(text, q); // count lines
1664 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1665 #ifdef CONFIG_FEATURE_VI_SEARCH
1666 } else if (*p == '/') { // a search pattern
1668 for (p++; *p; p++) {
1674 pat = (Byte *) xstrdup((char *) buf); // save copy of pattern
1677 q = char_search(dot, pat, FORWARD, FULL);
1679 *addr = count_lines(text, q);
1682 #endif /* CONFIG_FEATURE_VI_SEARCH */
1683 } else if (*p == '$') { // the last line in file
1685 q = begin_line(end - 1);
1686 *addr = count_lines(text, q);
1687 } else if (isdigit(*p)) { // specific line number
1688 sscanf((char *) p, "%d%n", addr, &st);
1690 } else { // I don't reconise this
1691 // unrecognised address- assume -1
1697 static Byte *get_address(Byte *p, int *b, int *e) // get two colon addrs, if present
1699 //----- get the address' i.e., 1,3 'a,'b -----
1700 // get FIRST addr, if present
1702 p++; // skip over leading spaces
1703 if (*p == '%') { // alias for 1,$
1706 *e = count_lines(text, end-1);
1709 p = get_one_address(p, b);
1712 if (*p == ',') { // is there a address seperator
1716 // get SECOND addr, if present
1717 p = get_one_address(p, e);
1721 p++; // skip over trailing spaces
1725 static void colon(Byte * buf)
1727 Byte c, *orig_buf, *buf1, *q, *r;
1728 Byte *fn, cmd[BUFSIZ], args[BUFSIZ];
1729 int i, l, li, ch, st, b, e;
1730 int useforce, forced;
1733 // :3154 // if (-e line 3154) goto it else stay put
1734 // :4,33w! foo // write a portion of buffer to file "foo"
1735 // :w // write all of buffer to current file
1737 // :q! // quit- dont care about modified file
1738 // :'a,'z!sort -u // filter block through sort
1739 // :'f // goto mark "f"
1740 // :'fl // list literal the mark "f" line
1741 // :.r bar // read file "bar" into buffer before dot
1742 // :/123/,/abc/d // delete lines from "123" line to "abc" line
1743 // :/xyz/ // goto the "xyz" line
1744 // :s/find/replace/ // substitute pattern "find" with "replace"
1745 // :!<cmd> // run <cmd> then return
1747 if (strlen((char *) buf) <= 0)
1750 buf++; // move past the ':'
1752 forced = useforce = FALSE;
1753 li = st = ch = i = 0;
1755 q = text; // assume 1,$ for the range
1757 li = count_lines(text, end - 1);
1758 fn = cfn; // default to current file
1759 memset(cmd, '\0', BUFSIZ); // clear cmd[]
1760 memset(args, '\0', BUFSIZ); // clear args[]
1762 // look for optional address(es) :. :1 :1,9 :'q,'a :%
1763 buf = get_address(buf, &b, &e);
1765 // remember orig command line
1768 // get the COMMAND into cmd[]
1770 while (*buf != '\0') {
1775 // get any ARGuments
1776 while (isblnk(*buf))
1778 strcpy((char *) args, (char *) buf);
1779 buf1 = last_char_is((char *)cmd, '!');
1782 *buf1 = '\0'; // get rid of !
1785 // if there is only one addr, then the addr
1786 // is the line number of the single line the
1787 // user wants. So, reset the end
1788 // pointer to point at end of the "b" line
1789 q = find_line(b); // what line is #b
1794 // we were given two addrs. change the
1795 // end pointer to the addr given by user.
1796 r = find_line(e); // what line is #e
1800 // ------------ now look for the command ------------
1801 i = strlen((char *) cmd);
1802 if (i == 0) { // :123CR goto line #123
1804 dot = find_line(b); // what line is #b
1807 } else if (strncmp((char *) cmd, "!", 1) == 0) { // run a cmd
1808 // :!ls run the <cmd>
1809 (void) alarm(0); // wait for input- no alarms
1810 place_cursor(rows - 1, 0, FALSE); // go to Status line
1811 clear_to_eol(); // clear the line
1813 system(orig_buf+1); // run the cmd
1815 Hit_Return(); // let user see results
1816 (void) alarm(3); // done waiting for input
1817 } else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address
1818 if (b < 0) { // no addr given- use defaults
1819 b = e = count_lines(text, dot);
1822 } else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines
1823 if (b < 0) { // no addr given- use defaults
1824 q = begin_line(dot); // assume .,. for the range
1827 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
1829 } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file
1832 // don't edit, if the current file has been modified
1833 if (file_modified && ! useforce) {
1834 psbs("No write since last change (:edit! overrides)");
1837 if (strlen(args) > 0) {
1838 // the user supplied a file name
1840 } else if (cfn != 0 && strlen(cfn) > 0) {
1841 // no user supplied name- use the current filename
1845 // no user file name, no current name- punt
1846 psbs("No current filename");
1850 // see if file exists- if not, its just a new file request
1851 if ((sr=stat((char*)fn, &st_buf)) < 0) {
1852 // This is just a request for a new file creation.
1853 // The file_insert below will fail but we get
1854 // an empty buffer with a file name. Then the "write"
1855 // command can do the create.
1857 if ((st_buf.st_mode & (S_IFREG)) == 0) {
1858 // This is not a regular file
1859 psbs("\"%s\" is not a regular file", fn);
1862 if ((st_buf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
1863 // dont have any read permissions
1864 psbs("\"%s\" is not readable", fn);
1869 // There is a read-able regular file
1870 // make this the current file
1871 q = (Byte *) xstrdup((char *) fn); // save the cfn
1873 free(cfn); // free the old name
1874 cfn = q; // remember new cfn
1877 // delete all the contents of text[]
1878 new_text(2 * file_size(fn));
1879 screenbegin = dot = end = text;
1882 ch = file_insert(fn, text, file_size(fn));
1885 // start empty buf with dummy line
1886 (void) char_insert(text, '\n');
1889 file_modified = FALSE;
1890 #ifdef CONFIG_FEATURE_VI_YANKMARK
1891 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) {
1892 free(reg[Ureg]); // free orig line reg- for 'U'
1895 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) {
1896 free(reg[YDreg]); // free default yank/delete register
1899 for (li = 0; li < 28; li++) {
1902 #endif /* CONFIG_FEATURE_VI_YANKMARK */
1903 // how many lines in text[]?
1904 li = count_lines(text, end - 1);
1906 #ifdef CONFIG_FEATURE_VI_READONLY
1908 #endif /* CONFIG_FEATURE_VI_READONLY */
1910 (sr < 0 ? " [New file]" : ""),
1911 #ifdef CONFIG_FEATURE_VI_READONLY
1912 ((vi_readonly || readonly) ? " [Read only]" : ""),
1913 #endif /* CONFIG_FEATURE_VI_READONLY */
1915 } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this
1916 if (b != -1 || e != -1) {
1917 ni((Byte *) "No address allowed on this command");
1920 if (strlen((char *) args) > 0) {
1921 // user wants a new filename
1924 cfn = (Byte *) xstrdup((char *) args);
1926 // user wants file status info
1929 } else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available
1930 // print out values of all features
1931 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
1932 clear_to_eol(); // clear the line
1937 } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line
1938 if (b < 0) { // no addr given- use defaults
1939 q = begin_line(dot); // assume .,. for the range
1942 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
1943 clear_to_eol(); // clear the line
1944 write(1, "\r\n", 2);
1945 for (; q <= r; q++) {
1951 } else if (*q < ' ') {
1959 #ifdef CONFIG_FEATURE_VI_SET
1961 #endif /* CONFIG_FEATURE_VI_SET */
1963 } else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit
1964 (strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file
1966 // force end of argv list
1973 // don't exit if the file been modified
1974 if (file_modified) {
1975 psbs("No write since last change (:%s! overrides)",
1976 (*cmd == 'q' ? "quit" : "next"));
1979 // are there other file to edit
1980 if (*cmd == 'q' && optind < save_argc - 1) {
1981 psbs("%d more file to edit", (save_argc - optind - 1));
1984 if (*cmd == 'n' && optind >= save_argc - 1) {
1985 psbs("No more files to edit");
1989 } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[]
1991 if (strlen((char *) fn) <= 0) {
1992 psbs("No filename given");
1995 if (b < 0) { // no addr given- use defaults
1996 q = begin_line(dot); // assume "dot"
1998 // read after current line- unless user said ":0r foo"
2001 #ifdef CONFIG_FEATURE_VI_READONLY
2002 l= readonly; // remember current files' status
2004 ch = file_insert(fn, q, file_size(fn));
2005 #ifdef CONFIG_FEATURE_VI_READONLY
2009 goto vc1; // nothing was inserted
2010 // how many lines in text[]?
2011 li = count_lines(q, q + ch - 1);
2013 #ifdef CONFIG_FEATURE_VI_READONLY
2015 #endif /* CONFIG_FEATURE_VI_READONLY */
2017 #ifdef CONFIG_FEATURE_VI_READONLY
2018 ((vi_readonly || readonly) ? " [Read only]" : ""),
2019 #endif /* CONFIG_FEATURE_VI_READONLY */
2022 // if the insert is before "dot" then we need to update
2025 file_modified = TRUE;
2027 } else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args
2028 if (file_modified && ! useforce) {
2029 psbs("No write since last change (:rewind! overrides)");
2031 // reset the filenames to edit
2032 optind = fn_start - 1;
2035 #ifdef CONFIG_FEATURE_VI_SET
2036 } else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features
2037 i = 0; // offset into args
2038 if (strlen((char *) args) == 0) {
2039 // print out values of all options
2040 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2041 clear_to_eol(); // clear the line
2042 printf("----------------------------------------\r\n");
2043 #ifdef CONFIG_FEATURE_VI_SETOPTS
2046 printf("autoindent ");
2052 printf("ignorecase ");
2055 printf("showmatch ");
2056 printf("tabstop=%d ", tabstop);
2057 #endif /* CONFIG_FEATURE_VI_SETOPTS */
2061 if (strncasecmp((char *) args, "no", 2) == 0)
2062 i = 2; // ":set noautoindent"
2063 #ifdef CONFIG_FEATURE_VI_SETOPTS
2064 if (strncasecmp((char *) args + i, "autoindent", 10) == 0 ||
2065 strncasecmp((char *) args + i, "ai", 2) == 0) {
2066 autoindent = (i == 2) ? 0 : 1;
2068 if (strncasecmp((char *) args + i, "flash", 5) == 0 ||
2069 strncasecmp((char *) args + i, "fl", 2) == 0) {
2070 err_method = (i == 2) ? 0 : 1;
2072 if (strncasecmp((char *) args + i, "ignorecase", 10) == 0 ||
2073 strncasecmp((char *) args + i, "ic", 2) == 0) {
2074 ignorecase = (i == 2) ? 0 : 1;
2076 if (strncasecmp((char *) args + i, "showmatch", 9) == 0 ||
2077 strncasecmp((char *) args + i, "sm", 2) == 0) {
2078 showmatch = (i == 2) ? 0 : 1;
2080 if (strncasecmp((char *) args + i, "tabstop", 7) == 0) {
2081 sscanf(strchr((char *) args + i, '='), "=%d", &ch);
2082 if (ch > 0 && ch < columns - 1)
2085 #endif /* CONFIG_FEATURE_VI_SETOPTS */
2086 #endif /* CONFIG_FEATURE_VI_SET */
2087 #ifdef CONFIG_FEATURE_VI_SEARCH
2088 } else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
2092 // F points to the "find" pattern
2093 // R points to the "replace" pattern
2094 // replace the cmd line delimiters "/" with NULLs
2095 gflag = 0; // global replace flag
2096 c = orig_buf[1]; // what is the delimiter
2097 F = orig_buf + 2; // start of "find"
2098 R = (Byte *) strchr((char *) F, c); // middle delimiter
2099 if (!R) goto colon_s_fail;
2100 *R++ = '\0'; // terminate "find"
2101 buf1 = (Byte *) strchr((char *) R, c);
2102 if (!buf1) goto colon_s_fail;
2103 *buf1++ = '\0'; // terminate "replace"
2104 if (*buf1 == 'g') { // :s/foo/bar/g
2106 gflag++; // turn on gflag
2109 if (b < 0) { // maybe :s/foo/bar/
2110 q = begin_line(dot); // start with cur line
2111 b = count_lines(text, q); // cur line number
2114 e = b; // maybe :.s/foo/bar/
2115 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
2116 ls = q; // orig line start
2118 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
2120 // we found the "find" pattern- delete it
2121 (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1);
2122 // inset the "replace" patern
2123 (void) string_insert(buf1, R); // insert the string
2124 // check for "global" :s/foo/bar/g
2126 if ((buf1 + strlen((char *) R)) < end_line(ls)) {
2127 q = buf1 + strlen((char *) R);
2128 goto vc4; // don't let q move past cur line
2134 #endif /* CONFIG_FEATURE_VI_SEARCH */
2135 } else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version
2136 psb("%s", vi_Version);
2137 } else if ((strncasecmp((char *) cmd, "write", i) == 0) || // write text to file
2138 (strncasecmp((char *) cmd, "wq", i) == 0) ||
2139 (strncasecmp((char *) cmd, "x", i) == 0)) {
2140 // is there a file name to write to?
2141 if (strlen((char *) args) > 0) {
2144 #ifdef CONFIG_FEATURE_VI_READONLY
2145 if ((vi_readonly || readonly) && ! useforce) {
2146 psbs("\"%s\" File is read only", fn);
2149 #endif /* CONFIG_FEATURE_VI_READONLY */
2150 // how many lines in text[]?
2151 li = count_lines(q, r);
2153 // see if file exists- if not, its just a new file request
2155 // if "fn" is not write-able, chmod u+w
2156 // sprintf(syscmd, "chmod u+w %s", fn);
2160 l = file_write(fn, q, r);
2161 if (useforce && forced) {
2163 // sprintf(syscmd, "chmod u-w %s", fn);
2167 psb("\"%s\" %dL, %dC", fn, li, l);
2168 if (q == text && r == end - 1 && l == ch)
2169 file_modified = FALSE;
2170 if ((cmd[0] == 'x' || cmd[1] == 'q') && l == ch) {
2173 #ifdef CONFIG_FEATURE_VI_READONLY
2175 #endif /* CONFIG_FEATURE_VI_READONLY */
2176 #ifdef CONFIG_FEATURE_VI_YANKMARK
2177 } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines
2178 if (b < 0) { // no addr given- use defaults
2179 q = begin_line(dot); // assume .,. for the range
2182 text_yank(q, r, YDreg);
2183 li = count_lines(q, r);
2184 psb("Yank %d lines (%d chars) into [%c]",
2185 li, strlen((char *) reg[YDreg]), what_reg());
2186 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2192 dot = bound_dot(dot); // make sure "dot" is valid
2194 #ifdef CONFIG_FEATURE_VI_SEARCH
2196 psb(":s expression missing delimiters");
2202 static void Hit_Return(void)
2206 standout_start(); // start reverse video
2207 write(1, "[Hit return to continue]", 24);
2208 standout_end(); // end reverse video
2209 while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */
2211 redraw(TRUE); // force redraw all
2213 #endif /* CONFIG_FEATURE_VI_COLON */
2215 //----- Synchronize the cursor to Dot --------------------------
2216 static void sync_cursor(Byte * d, int *row, int *col)
2218 Byte *beg_cur, *end_cur; // begin and end of "d" line
2219 Byte *beg_scr, *end_scr; // begin and end of screen
2223 beg_cur = begin_line(d); // first char of cur line
2224 end_cur = end_line(d); // last char of cur line
2226 beg_scr = end_scr = screenbegin; // first char of screen
2227 end_scr = end_screen(); // last char of screen
2229 if (beg_cur < screenbegin) {
2230 // "d" is before top line on screen
2231 // how many lines do we have to move
2232 cnt = count_lines(beg_cur, screenbegin);
2234 screenbegin = beg_cur;
2235 if (cnt > (rows - 1) / 2) {
2236 // we moved too many lines. put "dot" in middle of screen
2237 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
2238 screenbegin = prev_line(screenbegin);
2241 } else if (beg_cur > end_scr) {
2242 // "d" is after bottom line on screen
2243 // how many lines do we have to move
2244 cnt = count_lines(end_scr, beg_cur);
2245 if (cnt > (rows - 1) / 2)
2246 goto sc1; // too many lines
2247 for (ro = 0; ro < cnt - 1; ro++) {
2248 // move screen begin the same amount
2249 screenbegin = next_line(screenbegin);
2250 // now, move the end of screen
2251 end_scr = next_line(end_scr);
2252 end_scr = end_line(end_scr);
2255 // "d" is on screen- find out which row
2257 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
2263 // find out what col "d" is on
2265 do { // drive "co" to correct column
2266 if (*tp == '\n' || *tp == '\0')
2270 co += ((tabstop - 1) - (co % tabstop));
2271 } else if (*tp < ' ') {
2272 co++; // display as ^X, use 2 columns
2274 } while (tp++ < d && ++co);
2276 // "co" is the column where "dot" is.
2277 // The screen has "columns" columns.
2278 // The currently displayed columns are 0+offset -- columns+ofset
2279 // |-------------------------------------------------------------|
2281 // offset | |------- columns ----------------|
2283 // If "co" is already in this range then we do not have to adjust offset
2284 // but, we do have to subtract the "offset" bias from "co".
2285 // If "co" is outside this range then we have to change "offset".
2286 // If the first char of a line is a tab the cursor will try to stay
2287 // in column 7, but we have to set offset to 0.
2289 if (co < 0 + offset) {
2292 if (co >= columns + offset) {
2293 offset = co - columns + 1;
2295 // if the first char of the line is a tab, and "dot" is sitting on it
2296 // force offset to 0.
2297 if (d == beg_cur && *d == '\t') {
2306 //----- Text Movement Routines ---------------------------------
2307 static Byte *begin_line(Byte * p) // return pointer to first char cur line
2309 while (p > text && p[-1] != '\n')
2310 p--; // go to cur line B-o-l
2314 static Byte *end_line(Byte * p) // return pointer to NL of cur line line
2316 while (p < end - 1 && *p != '\n')
2317 p++; // go to cur line E-o-l
2321 static Byte *dollar_line(Byte * p) // return pointer to just before NL line
2323 while (p < end - 1 && *p != '\n')
2324 p++; // go to cur line E-o-l
2325 // Try to stay off of the Newline
2326 if (*p == '\n' && (p - begin_line(p)) > 0)
2331 static Byte *prev_line(Byte * p) // return pointer first char prev line
2333 p = begin_line(p); // goto begining of cur line
2334 if (p[-1] == '\n' && p > text)
2335 p--; // step to prev line
2336 p = begin_line(p); // goto begining of prev line
2340 static Byte *next_line(Byte * p) // return pointer first char next line
2343 if (*p == '\n' && p < end - 1)
2344 p++; // step to next line
2348 //----- Text Information Routines ------------------------------
2349 static Byte *end_screen(void)
2354 // find new bottom line
2356 for (cnt = 0; cnt < rows - 2; cnt++)
2362 static int count_lines(Byte * start, Byte * stop) // count line from start to stop
2367 if (stop < start) { // start and stop are backwards- reverse them
2373 stop = end_line(stop); // get to end of this line
2374 for (q = start; q <= stop && q <= end - 1; q++) {
2381 static Byte *find_line(int li) // find begining of line #li
2385 for (q = text; li > 1; li--) {
2391 //----- Dot Movement Routines ----------------------------------
2392 static void dot_left(void)
2394 if (dot > text && dot[-1] != '\n')
2398 static void dot_right(void)
2400 if (dot < end - 1 && *dot != '\n')
2404 static void dot_begin(void)
2406 dot = begin_line(dot); // return pointer to first char cur line
2409 static void dot_end(void)
2411 dot = end_line(dot); // return pointer to last char cur line
2414 static Byte *move_to_col(Byte * p, int l)
2421 if (*p == '\n' || *p == '\0')
2425 co += ((tabstop - 1) - (co % tabstop));
2426 } else if (*p < ' ') {
2427 co++; // display as ^X, use 2 columns
2429 } while (++co <= l && p++ < end);
2433 static void dot_next(void)
2435 dot = next_line(dot);
2438 static void dot_prev(void)
2440 dot = prev_line(dot);
2443 static void dot_scroll(int cnt, int dir)
2447 for (; cnt > 0; cnt--) {
2450 // ctrl-Y scroll up one line
2451 screenbegin = prev_line(screenbegin);
2454 // ctrl-E scroll down one line
2455 screenbegin = next_line(screenbegin);
2458 // make sure "dot" stays on the screen so we dont scroll off
2459 if (dot < screenbegin)
2461 q = end_screen(); // find new bottom line
2463 dot = begin_line(q); // is dot is below bottom line?
2467 static void dot_skip_over_ws(void)
2470 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
2474 static void dot_delete(void) // delete the char at 'dot'
2476 (void) text_hole_delete(dot, dot);
2479 static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end"
2481 if (p >= end && end > text) {
2483 indicate_error('1');
2487 indicate_error('2');
2492 //----- Helper Utility Routines --------------------------------
2494 //----------------------------------------------------------------
2495 //----- Char Routines --------------------------------------------
2496 /* Chars that are part of a word-
2497 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
2498 * Chars that are Not part of a word (stoppers)
2499 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
2500 * Chars that are WhiteSpace
2501 * TAB NEWLINE VT FF RETURN SPACE
2502 * DO NOT COUNT NEWLINE AS WHITESPACE
2505 static Byte *new_screen(int ro, int co)
2511 screensize = ro * co + 8;
2512 screen = (Byte *) xmalloc(screensize);
2513 // initialize the new screen. assume this will be a empty file.
2515 // non-existant text[] lines start with a tilde (~).
2516 for (li = 1; li < ro - 1; li++) {
2517 screen[(li * co) + 0] = '~';
2522 static Byte *new_text(int size)
2525 size = 10240; // have a minimum size for new files
2530 text = (Byte *) xmalloc(size + 8);
2531 memset(text, '\0', size); // clear new text[]
2532 //text += 4; // leave some room for "oops"
2533 textend = text + size - 1;
2534 //textend -= 4; // leave some root for "oops"
2538 #ifdef CONFIG_FEATURE_VI_SEARCH
2539 static int mycmp(Byte * s1, Byte * s2, int len)
2543 i = strncmp((char *) s1, (char *) s2, len);
2544 #ifdef CONFIG_FEATURE_VI_SETOPTS
2546 i = strncasecmp((char *) s1, (char *) s2, len);
2548 #endif /* CONFIG_FEATURE_VI_SETOPTS */
2552 static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p
2554 #ifndef REGEX_SEARCH
2558 len = strlen((char *) pat);
2559 if (dir == FORWARD) {
2560 stop = end - 1; // assume range is p - end-1
2561 if (range == LIMITED)
2562 stop = next_line(p); // range is to next line
2563 for (start = p; start < stop; start++) {
2564 if (mycmp(start, pat, len) == 0) {
2568 } else if (dir == BACK) {
2569 stop = text; // assume range is text - p
2570 if (range == LIMITED)
2571 stop = prev_line(p); // range is to prev line
2572 for (start = p - len; start >= stop; start--) {
2573 if (mycmp(start, pat, len) == 0) {
2578 // pattern not found
2580 #else /*REGEX_SEARCH */
2582 struct re_pattern_buffer preg;
2586 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
2592 // assume a LIMITED forward search
2600 // count the number of chars to search over, forward or backward
2604 // RANGE could be negative if we are searching backwards
2607 q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
2609 // The pattern was not compiled
2610 psbs("bad search pattern: \"%s\": %s", pat, q);
2611 i = 0; // return p if pattern not compiled
2621 // search for the compiled pattern, preg, in p[]
2622 // range < 0- search backward
2623 // range > 0- search forward
2625 // re_search() < 0 not found or error
2626 // re_search() > 0 index of found pattern
2627 // struct pattern char int int int struct reg
2628 // re_search (*pattern_buffer, *string, size, start, range, *regs)
2629 i = re_search(&preg, q, size, 0, range, 0);
2632 i = 0; // return NULL if pattern not found
2635 if (dir == FORWARD) {
2641 #endif /*REGEX_SEARCH */
2643 #endif /* CONFIG_FEATURE_VI_SEARCH */
2645 static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
2647 if (c == 22) { // Is this an ctrl-V?
2648 p = stupid_insert(p, '^'); // use ^ to indicate literal next
2649 p--; // backup onto ^
2650 refresh(FALSE); // show the ^
2654 file_modified = TRUE; // has the file been modified
2655 } else if (c == 27) { // Is this an ESC?
2658 end_cmd_q(); // stop adding to q
2659 strcpy((char *) status_buffer, " "); // clear the status buffer
2660 if ((p[-1] != '\n') && (dot>text)) {
2663 } else if (c == erase_char) { // Is this a BS
2665 if ((p[-1] != '\n') && (dot>text)) {
2667 p = text_hole_delete(p, p); // shrink buffer 1 char
2668 #ifdef CONFIG_FEATURE_VI_DOT_CMD
2669 // also rmove char from last_modifying_cmd
2670 if (strlen((char *) last_modifying_cmd) > 0) {
2673 q = last_modifying_cmd;
2674 q[strlen((char *) q) - 1] = '\0'; // erase BS
2675 q[strlen((char *) q) - 1] = '\0'; // erase prev char
2677 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
2680 // insert a char into text[]
2681 Byte *sp; // "save p"
2684 c = '\n'; // translate \r to \n
2685 sp = p; // remember addr of insert
2686 p = stupid_insert(p, c); // insert the char
2687 #ifdef CONFIG_FEATURE_VI_SETOPTS
2688 if (showmatch && strchr(")]}", *sp) != NULL) {
2691 if (autoindent && c == '\n') { // auto indent the new line
2694 q = prev_line(p); // use prev line as templet
2695 for (; isblnk(*q); q++) {
2696 p = stupid_insert(p, *q); // insert the char
2699 #endif /* CONFIG_FEATURE_VI_SETOPTS */
2704 static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
2706 p = text_hole_make(p, 1);
2709 file_modified = TRUE; // has the file been modified
2715 static Byte find_range(Byte ** start, Byte ** stop, Byte c)
2717 Byte *save_dot, *p, *q;
2723 if (strchr("cdy><", c)) {
2724 // these cmds operate on whole lines
2725 p = q = begin_line(p);
2726 for (cnt = 1; cnt < cmdcnt; cnt++) {
2730 } else if (strchr("^%$0bBeEft", c)) {
2731 // These cmds operate on char positions
2732 do_cmd(c); // execute movement cmd
2734 } else if (strchr("wW", c)) {
2735 do_cmd(c); // execute movement cmd
2737 dot--; // move back off of next word
2738 if (dot > text && *dot == '\n')
2739 dot--; // stay off NL
2741 } else if (strchr("H-k{", c)) {
2742 // these operate on multi-lines backwards
2743 q = end_line(dot); // find NL
2744 do_cmd(c); // execute movement cmd
2747 } else if (strchr("L+j}\r\n", c)) {
2748 // these operate on multi-lines forwards
2749 p = begin_line(dot);
2750 do_cmd(c); // execute movement cmd
2751 dot_end(); // find NL
2754 c = 27; // error- return an ESC char
2767 static int st_test(Byte * p, int type, int dir, Byte * tested)
2777 if (type == S_BEFORE_WS) {
2779 test = ((!isspace(c)) || c == '\n');
2781 if (type == S_TO_WS) {
2783 test = ((!isspace(c)) || c == '\n');
2785 if (type == S_OVER_WS) {
2787 test = ((isspace(c)));
2789 if (type == S_END_PUNCT) {
2791 test = ((ispunct(c)));
2793 if (type == S_END_ALNUM) {
2795 test = ((isalnum(c)) || c == '_');
2801 static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
2805 while (st_test(p, type, dir, &c)) {
2806 // make sure we limit search to correct number of lines
2807 if (c == '\n' && --linecnt < 1)
2809 if (dir >= 0 && p >= end - 1)
2811 if (dir < 0 && p <= text)
2813 p += dir; // move to next char
2818 // find matching char of pair () [] {}
2819 static Byte *find_pair(Byte * p, Byte c)
2826 dir = 1; // assume forward
2850 for (q = p + dir; text <= q && q < end; q += dir) {
2851 // look for match, count levels of pairs (( ))
2853 level++; // increase pair levels
2855 level--; // reduce pair level
2857 break; // found matching pair
2860 q = NULL; // indicate no match
2864 #ifdef CONFIG_FEATURE_VI_SETOPTS
2865 // show the matching char of a pair, () [] {}
2866 static void showmatching(Byte * p)
2870 // we found half of a pair
2871 q = find_pair(p, *p); // get loc of matching char
2873 indicate_error('3'); // no matching char
2875 // "q" now points to matching pair
2876 save_dot = dot; // remember where we are
2877 dot = q; // go to new loc
2878 refresh(FALSE); // let the user see it
2879 (void) mysleep(40); // give user some time
2880 dot = save_dot; // go back to old loc
2884 #endif /* CONFIG_FEATURE_VI_SETOPTS */
2886 // open a hole in text[]
2887 static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
2896 cnt = end - src; // the rest of buffer
2897 if (memmove(dest, src, cnt) != dest) {
2898 psbs("can't create room for new characters");
2900 memset(p, ' ', size); // clear new hole
2901 end = end + size; // adjust the new END
2902 file_modified = TRUE; // has the file been modified
2907 // close a hole in text[]
2908 static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
2913 // move forwards, from beginning
2917 if (q < p) { // they are backward- swap them
2921 hole_size = q - p + 1;
2923 if (src < text || src > end)
2925 if (dest < text || dest >= end)
2928 goto thd_atend; // just delete the end of the buffer
2929 if (memmove(dest, src, cnt) != dest) {
2930 psbs("can't delete the character");
2933 end = end - hole_size; // adjust the new END
2935 dest = end - 1; // make sure dest in below end-1
2937 dest = end = text; // keep pointers valid
2938 file_modified = TRUE; // has the file been modified
2943 // copy text into register, then delete text.
2944 // if dist <= 0, do not include, or go past, a NewLine
2946 static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
2950 // make sure start <= stop
2952 // they are backwards, reverse them
2958 // we can not cross NL boundaries
2962 // dont go past a NewLine
2963 for (; p + 1 <= stop; p++) {
2965 stop = p; // "stop" just before NewLine
2971 #ifdef CONFIG_FEATURE_VI_YANKMARK
2972 text_yank(start, stop, YDreg);
2973 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2974 if (yf == YANKDEL) {
2975 p = text_hole_delete(start, stop);
2980 static void show_help(void)
2982 puts("These features are available:"
2983 #ifdef CONFIG_FEATURE_VI_SEARCH
2984 "\n\tPattern searches with / and ?"
2985 #endif /* CONFIG_FEATURE_VI_SEARCH */
2986 #ifdef CONFIG_FEATURE_VI_DOT_CMD
2987 "\n\tLast command repeat with \'.\'"
2988 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
2989 #ifdef CONFIG_FEATURE_VI_YANKMARK
2990 "\n\tLine marking with 'x"
2991 "\n\tNamed buffers with \"x"
2992 #endif /* CONFIG_FEATURE_VI_YANKMARK */
2993 #ifdef CONFIG_FEATURE_VI_READONLY
2994 "\n\tReadonly if vi is called as \"view\""
2995 "\n\tReadonly with -R command line arg"
2996 #endif /* CONFIG_FEATURE_VI_READONLY */
2997 #ifdef CONFIG_FEATURE_VI_SET
2998 "\n\tSome colon mode commands with \':\'"
2999 #endif /* CONFIG_FEATURE_VI_SET */
3000 #ifdef CONFIG_FEATURE_VI_SETOPTS
3001 "\n\tSettable options with \":set\""
3002 #endif /* CONFIG_FEATURE_VI_SETOPTS */
3003 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
3004 "\n\tSignal catching- ^C"
3005 "\n\tJob suspend and resume with ^Z"
3006 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
3007 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
3008 "\n\tAdapt to window re-sizes"
3009 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
3013 static void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
3018 strcpy((char *) buf, ""); // init buf
3019 if (strlen((char *) s) <= 0)
3020 s = (Byte *) "(NULL)";
3021 for (; *s > '\0'; s++) {
3024 strcat((char *) buf, SOs);
3028 strcat((char *) buf, "^");
3032 strcat((char *) buf, (char *) b);
3034 strcat((char *) buf, SOn);
3036 strcat((char *) buf, "$");
3041 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3042 static void start_new_cmd_q(Byte c)
3045 if (last_modifying_cmd != 0)
3046 free(last_modifying_cmd);
3047 // get buffer for new cmd
3048 last_modifying_cmd = (Byte *) xmalloc(BUFSIZ);
3049 memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue
3050 // if there is a current cmd count put it in the buffer first
3052 sprintf((char *) last_modifying_cmd, "%d", cmdcnt);
3053 // save char c onto queue
3054 last_modifying_cmd[strlen((char *) last_modifying_cmd)] = c;
3059 static void end_cmd_q(void)
3061 #ifdef CONFIG_FEATURE_VI_YANKMARK
3062 YDreg = 26; // go back to default Yank/Delete reg
3063 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3067 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3069 #if defined(CONFIG_FEATURE_VI_YANKMARK) || defined(CONFIG_FEATURE_VI_COLON) || defined(CONFIG_FEATURE_VI_CRASHME)
3070 static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
3074 i = strlen((char *) s);
3075 p = text_hole_make(p, i);
3076 strncpy((char *) p, (char *) s, i);
3077 for (cnt = 0; *s != '\0'; s++) {
3081 #ifdef CONFIG_FEATURE_VI_YANKMARK
3082 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
3083 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3086 #endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */
3088 #ifdef CONFIG_FEATURE_VI_YANKMARK
3089 static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register
3094 if (q < p) { // they are backwards- reverse them
3101 if (t != 0) { // if already a yank register
3104 t = (Byte *) xmalloc(cnt + 1); // get a new register
3105 memset(t, '\0', cnt + 1); // clear new text[]
3106 strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer
3111 static Byte what_reg(void)
3117 c = 'D'; // default to D-reg
3118 if (0 <= YDreg && YDreg <= 25)
3119 c = 'a' + (Byte) YDreg;
3127 static void check_context(Byte cmd)
3129 // A context is defined to be "modifying text"
3130 // Any modifying command establishes a new context.
3132 if (dot < context_start || dot > context_end) {
3133 if (strchr((char *) modifying_cmds, cmd) != NULL) {
3134 // we are trying to modify text[]- make this the current context
3135 mark[27] = mark[26]; // move cur to prev
3136 mark[26] = dot; // move local to cur
3137 context_start = prev_line(prev_line(dot));
3138 context_end = next_line(next_line(dot));
3139 //loiter= start_loiter= now;
3145 static Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
3149 // the current context is in mark[26]
3150 // the previous context is in mark[27]
3151 // only swap context if other context is valid
3152 if (text <= mark[27] && mark[27] <= end - 1) {
3154 mark[27] = mark[26];
3156 p = mark[26]; // where we are going- previous context
3157 context_start = prev_line(prev_line(prev_line(p)));
3158 context_end = next_line(next_line(next_line(p)));
3162 #endif /* CONFIG_FEATURE_VI_YANKMARK */
3164 static int isblnk(Byte c) // is the char a blank or tab
3166 return (c == ' ' || c == '\t');
3169 //----- Set terminal attributes --------------------------------
3170 static void rawmode(void)
3172 tcgetattr(0, &term_orig);
3173 term_vi = term_orig;
3174 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
3175 term_vi.c_iflag &= (~IXON & ~ICRNL);
3176 term_vi.c_oflag &= (~ONLCR);
3178 term_vi.c_cc[VMIN] = 1;
3179 term_vi.c_cc[VTIME] = 0;
3181 erase_char = term_vi.c_cc[VERASE];
3182 tcsetattr(0, TCSANOW, &term_vi);
3185 static void cookmode(void)
3187 tcsetattr(0, TCSANOW, &term_orig);
3190 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
3191 //----- See what the window size currently is --------------------
3192 static void window_size_get(int sig)
3196 i = ioctl(0, TIOCGWINSZ, &winsize);
3199 winsize.ws_row = 24;
3200 winsize.ws_col = 80;
3202 if (winsize.ws_row <= 1) {
3203 winsize.ws_row = 24;
3205 if (winsize.ws_col <= 1) {
3206 winsize.ws_col = 80;
3208 rows = (int) winsize.ws_row;
3209 columns = (int) winsize.ws_col;
3211 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
3213 //----- Come here when we get a window resize signal ---------
3214 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
3215 static void winch_sig(int sig)
3217 signal(SIGWINCH, winch_sig);
3218 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
3220 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
3221 new_screen(rows, columns); // get memory for virtual screen
3222 redraw(TRUE); // re-draw the screen
3225 //----- Come here when we get a continue signal -------------------
3226 static void cont_sig(int sig)
3228 rawmode(); // terminal to "raw"
3229 *status_buffer = '\0'; // clear the status buffer
3230 redraw(TRUE); // re-draw the screen
3232 signal(SIGTSTP, suspend_sig);
3233 signal(SIGCONT, SIG_DFL);
3234 kill(getpid(), SIGCONT);
3237 //----- Come here when we get a Suspend signal -------------------
3238 static void suspend_sig(int sig)
3240 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
3241 clear_to_eol(); // Erase to end of line
3242 cookmode(); // terminal to "cooked"
3244 signal(SIGCONT, cont_sig);
3245 signal(SIGTSTP, SIG_DFL);
3246 kill(getpid(), SIGTSTP);
3249 //----- Come here when we get a signal ---------------------------
3250 static void catch_sig(int sig)
3252 signal(SIGHUP, catch_sig);
3253 signal(SIGINT, catch_sig);
3254 signal(SIGTERM, catch_sig);
3255 longjmp(restart, sig);
3258 static void alarm_sig(int sig)
3260 signal(SIGALRM, catch_sig);
3261 longjmp(restart, sig);
3264 //----- Come here when we get a core dump signal -----------------
3265 static void core_sig(int sig)
3267 signal(SIGQUIT, core_sig);
3268 signal(SIGILL, core_sig);
3269 signal(SIGTRAP, core_sig);
3270 signal(SIGIOT, core_sig);
3271 signal(SIGABRT, core_sig);
3272 signal(SIGFPE, core_sig);
3273 signal(SIGBUS, core_sig);
3274 signal(SIGSEGV, core_sig);
3276 signal(SIGSYS, core_sig);
3279 dot = bound_dot(dot); // make sure "dot" is valid
3281 longjmp(restart, sig);
3283 #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
3285 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
3287 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
3291 tv.tv_usec = hund * 10000;
3292 select(1, &rfds, NULL, NULL, &tv);
3293 return (FD_ISSET(0, &rfds));
3296 //----- IO Routines --------------------------------------------
3297 static Byte readit(void) // read (maybe cursor) key from stdin
3300 int i, bufsiz, cnt, cmdindex;
3306 static struct esc_cmds esccmds[] = {
3307 {(Byte *) "
\eOA", (Byte) VI_K_UP}, // cursor key Up
3308 {(Byte *) "
\eOB", (Byte) VI_K_DOWN}, // cursor key Down
3309 {(Byte *) "
\eOC", (Byte) VI_K_RIGHT}, // Cursor Key Right
3310 {(Byte *) "
\eOD", (Byte) VI_K_LEFT}, // cursor key Left
3311 {(Byte *) "
\eOH", (Byte) VI_K_HOME}, // Cursor Key Home
3312 {(Byte *) "
\eOF", (Byte) VI_K_END}, // Cursor Key End
3313 {(Byte *) "
\e[A", (Byte) VI_K_UP}, // cursor key Up
3314 {(Byte *) "
\e[B", (Byte) VI_K_DOWN}, // cursor key Down
3315 {(Byte *) "
\e[C", (Byte) VI_K_RIGHT}, // Cursor Key Right
3316 {(Byte *) "
\e[D", (Byte) VI_K_LEFT}, // cursor key Left
3317 {(Byte *) "
\e[H", (Byte) VI_K_HOME}, // Cursor Key Home
3318 {(Byte *) "
\e[F", (Byte) VI_K_END}, // Cursor Key End
3319 {(Byte *) "
\e[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert
3320 {(Byte *) "
\e[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up
3321 {(Byte *) "
\e[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down
3322 {(Byte *) "
\eOP", (Byte) VI_K_FUN1}, // Function Key F1
3323 {(Byte *) "
\eOQ", (Byte) VI_K_FUN2}, // Function Key F2
3324 {(Byte *) "
\eOR", (Byte) VI_K_FUN3}, // Function Key F3
3325 {(Byte *) "
\eOS", (Byte) VI_K_FUN4}, // Function Key F4
3326 {(Byte *) "
\e[15~", (Byte) VI_K_FUN5}, // Function Key F5
3327 {(Byte *) "
\e[17~", (Byte) VI_K_FUN6}, // Function Key F6
3328 {(Byte *) "
\e[18~", (Byte) VI_K_FUN7}, // Function Key F7
3329 {(Byte *) "
\e[19~", (Byte) VI_K_FUN8}, // Function Key F8
3330 {(Byte *) "
\e[20~", (Byte) VI_K_FUN9}, // Function Key F9
3331 {(Byte *) "
\e[21~", (Byte) VI_K_FUN10}, // Function Key F10
3332 {(Byte *) "
\e[23~", (Byte) VI_K_FUN11}, // Function Key F11
3333 {(Byte *) "
\e[24~", (Byte) VI_K_FUN12}, // Function Key F12
3334 {(Byte *) "
\e[11~", (Byte) VI_K_FUN1}, // Function Key F1
3335 {(Byte *) "
\e[12~", (Byte) VI_K_FUN2}, // Function Key F2
3336 {(Byte *) "
\e[13~", (Byte) VI_K_FUN3}, // Function Key F3
3337 {(Byte *) "
\e[14~", (Byte) VI_K_FUN4}, // Function Key F4
3340 #define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
3342 (void) alarm(0); // turn alarm OFF while we wait for input
3343 // get input from User- are there already input chars in Q?
3344 bufsiz = strlen((char *) readbuffer);
3347 // the Q is empty, wait for a typed char
3348 bufsiz = read(0, readbuffer, BUFSIZ - 1);
3351 goto ri0; // interrupted sys call
3354 if (errno == EFAULT)
3356 if (errno == EINVAL)
3363 readbuffer[bufsiz] = '\0';
3365 // return char if it is not part of ESC sequence
3366 if (readbuffer[0] != 27)
3369 // This is an ESC char. Is this Esc sequence?
3370 // Could be bare Esc key. See if there are any
3371 // more chars to read after the ESC. This would
3372 // be a Function or Cursor Key sequence.
3376 tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000
3378 // keep reading while there are input chars and room in buffer
3379 while (select(1, &rfds, NULL, NULL, &tv) > 0 && bufsiz <= (BUFSIZ - 5)) {
3380 // read the rest of the ESC string
3381 i = read(0, (void *) (readbuffer + bufsiz), BUFSIZ - bufsiz);
3384 readbuffer[bufsiz] = '\0'; // Terminate the string
3387 // Maybe cursor or function key?
3388 for (cmdindex = 0; cmdindex < ESCCMDS_COUNT; cmdindex++) {
3389 cnt = strlen((char *) esccmds[cmdindex].seq);
3390 i = strncmp((char *) esccmds[cmdindex].seq, (char *) readbuffer, cnt);
3392 // is a Cursor key- put derived value back into Q
3393 readbuffer[0] = esccmds[cmdindex].val;
3394 // squeeze out the ESC sequence
3395 for (i = 1; i < cnt; i++) {
3396 memmove(readbuffer + 1, readbuffer + 2, BUFSIZ - 2);
3397 readbuffer[BUFSIZ - 1] = '\0';
3404 // remove one char from Q
3405 memmove(readbuffer, readbuffer + 1, BUFSIZ - 1);
3406 readbuffer[BUFSIZ - 1] = '\0';
3407 (void) alarm(3); // we are done waiting for input, turn alarm ON
3411 //----- IO Routines --------------------------------------------
3412 static Byte get_one_char()
3416 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3417 // ! adding2q && ioq == 0 read()
3418 // ! adding2q && ioq != 0 *ioq
3419 // adding2q *last_modifying_cmd= read()
3421 // we are not adding to the q.
3422 // but, we may be reading from a q
3424 // there is no current q, read from STDIN
3425 c = readit(); // get the users input
3427 // there is a queue to get chars from first
3430 // the end of the q, read from STDIN
3432 ioq_start = ioq = 0;
3433 c = readit(); // get the users input
3437 // adding STDIN chars to q
3438 c = readit(); // get the users input
3439 if (last_modifying_cmd != 0) {
3440 // add new char to q
3441 last_modifying_cmd[strlen((char *) last_modifying_cmd)] = c;
3444 #else /* CONFIG_FEATURE_VI_DOT_CMD */
3445 c = readit(); // get the users input
3446 #endif /* CONFIG_FEATURE_VI_DOT_CMD */
3447 return (c); // return the char, where ever it came from
3450 static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
3455 static Byte *obufp = NULL;
3457 strcpy((char *) buf, (char *) prompt);
3458 *status_buffer = '\0'; // clear the status buffer
3459 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
3460 clear_to_eol(); // clear the line
3461 write(1, prompt, strlen((char *) prompt)); // write out the :, /, or ? prompt
3463 for (i = strlen((char *) buf); i < BUFSIZ;) {
3464 c = get_one_char(); // read user input
3465 if (c == '\n' || c == '\r' || c == 27)
3466 break; // is this end of input
3467 if (c == erase_char) { // user wants to erase prev char
3468 i--; // backup to prev char
3469 buf[i] = '\0'; // erase the char
3470 buf[i + 1] = '\0'; // null terminate buffer
3471 write(1, "
\b \b", 3); // erase char on screen
3472 if (i <= 0) { // user backs up before b-o-l, exit
3476 buf[i] = c; // save char in buffer
3477 buf[i + 1] = '\0'; // make sure buffer is null terminated
3478 write(1, buf + i, 1); // echo the char back to user
3485 obufp = (Byte *) xstrdup((char *) buf);
3489 static int file_size(Byte * fn) // what is the byte size of "fn"
3494 if (fn == 0 || strlen(fn) <= 0)
3497 sr = stat((char *) fn, &st_buf); // see if file exists
3499 cnt = (int) st_buf.st_size;
3504 static int file_insert(Byte * fn, Byte * p, int size)
3509 #ifdef CONFIG_FEATURE_VI_READONLY
3511 #endif /* CONFIG_FEATURE_VI_READONLY */
3512 if (fn == 0 || strlen((char*) fn) <= 0) {
3513 psbs("No filename given");
3517 // OK- this is just a no-op
3522 psbs("Trying to insert a negative number (%d) of characters", size);
3525 if (p < text || p > end) {
3526 psbs("Trying to insert file outside of memory");
3530 // see if we can open the file
3531 #ifdef CONFIG_FEATURE_VI_READONLY
3532 if (vi_readonly) goto fi1; // do not try write-mode
3534 fd = open((char *) fn, O_RDWR); // assume read & write
3536 // could not open for writing- maybe file is read only
3537 #ifdef CONFIG_FEATURE_VI_READONLY
3540 fd = open((char *) fn, O_RDONLY); // try read-only
3542 psbs("\"%s\" %s", fn, "could not open file");
3545 #ifdef CONFIG_FEATURE_VI_READONLY
3546 // got the file- read-only
3548 #endif /* CONFIG_FEATURE_VI_READONLY */
3550 p = text_hole_make(p, size);
3551 cnt = read(fd, p, size);
3555 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
3556 psbs("could not read file \"%s\"", fn);
3557 } else if (cnt < size) {
3558 // There was a partial read, shrink unused space text[]
3559 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
3560 psbs("could not read all of file \"%s\"", fn);
3563 file_modified = TRUE;
3568 static int file_write(Byte * fn, Byte * first, Byte * last)
3570 int fd, cnt, charcnt;
3573 psbs("No current filename");
3577 // FIXIT- use the correct umask()
3578 fd = open((char *) fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664);
3581 cnt = last - first + 1;
3582 charcnt = write(fd, first, cnt);
3583 if (charcnt == cnt) {
3585 //file_modified= FALSE; // the file has not been modified
3593 //----- Terminal Drawing ---------------------------------------
3594 // The terminal is made up of 'rows' line of 'columns' columns.
3595 // classicly this would be 24 x 80.
3596 // screen coordinates
3602 // 23,0 ... 23,79 status line
3605 //----- Move the cursor to row x col (count from 0, not 1) -------
3606 static void place_cursor(int row, int col, int opti)
3611 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
3614 // char cm3[BUFSIZ];
3616 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
3618 memset(cm1, '\0', BUFSIZ - 1); // clear the buffer
3620 if (row < 0) row = 0;
3621 if (row >= rows) row = rows - 1;
3622 if (col < 0) col = 0;
3623 if (col >= columns) col = columns - 1;
3625 //----- 1. Try the standard terminal ESC sequence
3626 sprintf((char *) cm1, CMrc, row + 1, col + 1);
3628 if (! opti) goto pc0;
3630 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
3631 //----- find the minimum # of chars to move cursor -------------
3632 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
3633 memset(cm2, '\0', BUFSIZ - 1); // clear the buffer
3635 // move to the correct row
3636 while (row < Rrow) {
3637 // the cursor has to move up
3641 while (row > Rrow) {
3642 // the cursor has to move down
3643 strcat(cm2, CMdown);
3647 // now move to the correct column
3648 strcat(cm2, "\r"); // start at col 0
3649 // just send out orignal source char to get to correct place
3650 screenp = &screen[row * columns]; // start of screen line
3651 strncat(cm2, screenp, col);
3653 //----- 3. Try some other way of moving cursor
3654 //---------------------------------------------
3656 // pick the shortest cursor motion to send out
3658 if (strlen(cm2) < strlen(cm)) {
3660 } /* else if (strlen(cm3) < strlen(cm)) {
3663 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
3666 if (l) write(1, cm, l); // move the cursor
3669 //----- Erase from cursor to end of line -----------------------
3670 static void clear_to_eol()
3672 write(1, Ceol, strlen(Ceol)); // Erase from cursor to end of line
3675 //----- Erase from cursor to end of screen -----------------------
3676 static void clear_to_eos()
3678 write(1, Ceos, strlen(Ceos)); // Erase from cursor to end of screen
3681 //----- Start standout mode ------------------------------------
3682 static void standout_start() // send "start reverse video" sequence
3684 write(1, SOs, strlen(SOs)); // Start reverse video mode
3687 //----- End standout mode --------------------------------------
3688 static void standout_end() // send "end reverse video" sequence
3690 write(1, SOn, strlen(SOn)); // End reverse video mode
3693 //----- Flash the screen --------------------------------------
3694 static void flash(int h)
3696 standout_start(); // send "start reverse video" sequence
3699 standout_end(); // send "end reverse video" sequence
3705 write(1, bell, strlen(bell)); // send out a bell character
3708 static void indicate_error(char c)
3710 #ifdef CONFIG_FEATURE_VI_CRASHME
3712 return; // generate a random command
3713 #endif /* CONFIG_FEATURE_VI_CRASHME */
3714 if (err_method == 0) {
3721 //----- Screen[] Routines --------------------------------------
3722 //----- Erase the Screen[] memory ------------------------------
3723 static void screen_erase()
3725 memset(screen, ' ', screensize); // clear new screen
3728 //----- Draw the status line at bottom of the screen -------------
3729 static void show_status_line(void)
3731 static int last_cksum;
3734 cnt = strlen((char *) status_buffer);
3735 for (cksum= l= 0; l < cnt; l++) { cksum += (int)(status_buffer[l]); }
3736 // don't write the status line unless it changes
3737 if (cnt > 0 && last_cksum != cksum) {
3738 last_cksum= cksum; // remember if we have seen this line
3739 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
3740 write(1, status_buffer, cnt);
3742 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
3746 //----- format the status buffer, the bottom line of screen ------
3747 // print status buffer, with STANDOUT mode
3748 static void psbs(char *format, ...)
3752 va_start(args, format);
3753 strcpy((char *) status_buffer, SOs); // Terminal standout mode on
3754 vsprintf((char *) status_buffer + strlen((char *) status_buffer), format,
3756 strcat((char *) status_buffer, SOn); // Terminal standout mode off
3762 // print status buffer
3763 static void psb(char *format, ...)
3767 va_start(args, format);
3768 vsprintf((char *) status_buffer, format, args);
3773 static void ni(Byte * s) // display messages
3777 print_literal(buf, s);
3778 psbs("\'%s\' is not implemented", buf);
3781 static void edit_status(void) // show file status on status line
3783 int cur, tot, percent;
3785 cur = count_lines(text, dot);
3786 tot = count_lines(text, end - 1);
3787 // current line percent
3788 // ------------- ~~ ----------
3791 percent = (100 * cur) / tot;
3797 #ifdef CONFIG_FEATURE_VI_READONLY
3799 #endif /* CONFIG_FEATURE_VI_READONLY */
3800 "%s line %d of %d --%d%%--",
3801 (cfn != 0 ? (char *) cfn : "No file"),
3802 #ifdef CONFIG_FEATURE_VI_READONLY
3803 ((vi_readonly || readonly) ? " [Read only]" : ""),
3804 #endif /* CONFIG_FEATURE_VI_READONLY */
3805 (file_modified ? " [modified]" : ""),
3809 //----- Force refresh of all Lines -----------------------------
3810 static void redraw(int full_screen)
3812 place_cursor(0, 0, FALSE); // put cursor in correct place
3813 clear_to_eos(); // tel terminal to erase display
3814 screen_erase(); // erase the internal screen buffer
3815 refresh(full_screen); // this will redraw the entire display
3818 //----- Format a text[] line into a buffer ---------------------
3819 static void format_line(Byte *dest, Byte *src, int li)
3824 for (co= 0; co < MAX_SCR_COLS; co++) {
3825 c= ' '; // assume blank
3826 if (li > 0 && co == 0) {
3827 c = '~'; // not first line, assume Tilde
3829 // are there chars in text[] and have we gone past the end
3830 if (text < end && src < end) {
3835 if (c < ' ' || c > '~') {
3839 for (; (co % tabstop) != (tabstop - 1); co++) {
3844 c |= '@'; // make it visible
3845 c &= 0x7f; // get rid of hi bit
3848 // the co++ is done here so that the column will
3849 // not be overwritten when we blank-out the rest of line
3856 //----- Refresh the changed screen lines -----------------------
3857 // Copy the source line from text[] into the buffer and note
3858 // if the current screenline is different from the new buffer.
3859 // If they differ then that line needs redrawing on the terminal.
3861 static void refresh(int full_screen)
3863 static int old_offset;
3865 Byte buf[MAX_SCR_COLS];
3866 Byte *tp, *sp; // pointer into text[] and screen[]
3867 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
3868 int last_li= -2; // last line that changed- for optimizing cursor movement
3869 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
3871 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
3873 #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
3874 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
3875 tp = screenbegin; // index into text[] of top line
3877 // compare text[] to screen[] and mark screen[] lines that need updating
3878 for (li = 0; li < rows - 1; li++) {
3879 int cs, ce; // column start & end
3880 memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer
3881 buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer
3882 // format current text line into buf
3883 format_line(buf, tp, li);
3885 // skip to the end of the current text[] line
3886 while (tp < end && *tp++ != '\n') /*no-op*/ ;
3888 // see if there are any changes between vitual screen and buf
3889 changed = FALSE; // assume no change
3892 sp = &screen[li * columns]; // start of screen line
3894 // force re-draw of every single column from 0 - columns-1
3897 // compare newly formatted buffer with virtual screen
3898 // look forward for first difference between buf and screen
3899 for ( ; cs <= ce; cs++) {
3900 if (buf[cs + offset] != sp[cs]) {
3901 changed = TRUE; // mark for redraw
3906 // look backward for last difference between buf and screen
3907 for ( ; ce >= cs; ce--) {
3908 if (buf[ce + offset] != sp[ce]) {
3909 changed = TRUE; // mark for redraw
3913 // now, cs is index of first diff, and ce is index of last diff
3915 // if horz offset has changed, force a redraw
3916 if (offset != old_offset) {
3921 // make a sanity check of columns indexes
3923 if (ce > columns-1) ce= columns-1;
3924 if (cs > ce) { cs= 0; ce= columns-1; }
3925 // is there a change between vitual screen and buf
3927 // copy changed part of buffer to virtual screen
3928 memmove(sp+cs, buf+(cs+offset), ce-cs+1);
3930 // move cursor to column of first change
3931 if (offset != old_offset) {
3932 // opti_cur_move is still too stupid
3933 // to handle offsets correctly
3934 place_cursor(li, cs, FALSE);
3936 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
3937 // if this just the next line
3938 // try to optimize cursor movement
3939 // otherwise, use standard ESC sequence
3940 place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE);
3942 #else /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
3943 place_cursor(li, cs, FALSE); // use standard ESC sequence
3944 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
3947 // write line out to terminal
3948 write(1, sp+cs, ce-cs+1);
3949 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
3951 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
3955 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
3956 place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE);
3959 place_cursor(crow, ccol, FALSE);
3960 #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
3962 if (offset != old_offset)
3963 old_offset = offset;